Add .gitignore and .ratignore files for various directories
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
gyurix
2025-04-29 21:43:12 +02:00
parent 983ecbfc53
commit be9f66dee9
2167 changed files with 254128 additions and 0 deletions

10
guacamole-common/.gitignore vendored Normal file
View File

@@ -0,0 +1,10 @@
# Compiled code
target/
# Backup files
*~
# Generated docs
doc/doxygen-output

View File

109
guacamole-common/pom.xml Normal file
View File

@@ -0,0 +1,109 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.apache.guacamole</groupId>
<artifactId>guacamole-common</artifactId>
<packaging>jar</packaging>
<version>1.6.0</version>
<name>guacamole-common</name>
<url>http://guacamole.apache.org/</url>
<parent>
<groupId>org.apache.guacamole</groupId>
<artifactId>guacamole-client</artifactId>
<version>1.6.0</version>
<relativePath>../</relativePath>
</parent>
<description>
The base Java API of the Guacamole project, providing Java support for
the Guacamole stack.
</description>
<!-- All applicable licenses -->
<licenses>
<license>
<name>Apache License, Version 2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<!-- Git repository -->
<scm>
<url>https://github.com/apache/guacamole-client</url>
<connection>scm:git:https://git.wip-us.apache.org/repos/asf/guacamole-client.git</connection>
</scm>
<build>
<plugins>
<!-- Attach source and JavaDoc .jars -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
</plugin>
</plugins>
</build>
<dependencies>
<!-- Java servlet API -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<!-- JSR 356 WebSocket API -->
<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>1.0</version>
<scope>provided</scope>
</dependency>
<!-- SLF4J - logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<!-- JUnit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,66 @@
/*
* 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;
import org.apache.guacamole.protocol.GuacamoleStatus;
/**
* An exception which is thrown when data has been submitted with an unsupported
* mimetype.
*/
public class GuacamoleClientBadTypeException extends GuacamoleClientException {
/**
* Creates a new GuacamoleClientBadTypeException with the given message and cause.
*
* @param message A human readable description of the exception that
* occurred.
* @param cause The cause of this exception.
*/
public GuacamoleClientBadTypeException(String message, Throwable cause) {
super(message, cause);
}
/**
* Creates a new GuacamoleClientBadTypeException with the given message.
*
* @param message A human readable description of the exception that
* occurred.
*/
public GuacamoleClientBadTypeException(String message) {
super(message);
}
/**
* Creates a new GuacamoleClientBadTypeException with the given cause.
*
* @param cause The cause of this exception.
*/
public GuacamoleClientBadTypeException(Throwable cause) {
super(cause);
}
@Override
public GuacamoleStatus getStatus() {
return GuacamoleStatus.CLIENT_BAD_TYPE;
}
}

View File

@@ -0,0 +1,67 @@
/*
* 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;
import org.apache.guacamole.protocol.GuacamoleStatus;
/**
* A generic exception thrown when part of the Guacamole API encounters
* an error in the client's request. Such an error, if correctable, usually
* requires correction on the client side, not the server.
*/
public class GuacamoleClientException extends GuacamoleException {
/**
* Creates a new GuacamoleException with the given message and cause.
*
* @param message A human readable description of the exception that
* occurred.
* @param cause The cause of this exception.
*/
public GuacamoleClientException(String message, Throwable cause) {
super(message, cause);
}
/**
* Creates a new GuacamoleException with the given message.
*
* @param message A human readable description of the exception that
* occurred.
*/
public GuacamoleClientException(String message) {
super(message);
}
/**
* Creates a new GuacamoleException with the given cause.
*
* @param cause The cause of this exception.
*/
public GuacamoleClientException(Throwable cause) {
super(cause);
}
@Override
public GuacamoleStatus getStatus() {
return GuacamoleStatus.CLIENT_BAD_REQUEST;
}
}

View File

@@ -0,0 +1,68 @@
/*
* 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;
import org.apache.guacamole.protocol.GuacamoleStatus;
/**
* An exception which is thrown when the client has sent too much data. This
* usually indicates that a server-side buffer is not large enough to
* accommodate the data, or protocol specifications prohibit data of the size
* received.
*/
public class GuacamoleClientOverrunException extends GuacamoleClientException {
/**
* Creates a new GuacamoleClientOverrunException with the given message and cause.
*
* @param message A human readable description of the exception that
* occurred.
* @param cause The cause of this exception.
*/
public GuacamoleClientOverrunException(String message, Throwable cause) {
super(message, cause);
}
/**
* Creates a new GuacamoleClientOverrunException with the given message.
*
* @param message A human readable description of the exception that
* occurred.
*/
public GuacamoleClientOverrunException(String message) {
super(message);
}
/**
* Creates a new GuacamoleClientOverrunException with the given cause.
*
* @param cause The cause of this exception.
*/
public GuacamoleClientOverrunException(Throwable cause) {
super(cause);
}
@Override
public GuacamoleStatus getStatus() {
return GuacamoleStatus.CLIENT_OVERRUN;
}
}

View File

@@ -0,0 +1,65 @@
/*
* 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;
import org.apache.guacamole.protocol.GuacamoleStatus;
/**
* An exception which is thrown when the client is taking too long to respond.
*/
public class GuacamoleClientTimeoutException extends GuacamoleClientException {
/**
* Creates a new GuacamoleClientTimeoutException with the given message and cause.
*
* @param message A human readable description of the exception that
* occurred.
* @param cause The cause of this exception.
*/
public GuacamoleClientTimeoutException(String message, Throwable cause) {
super(message, cause);
}
/**
* Creates a new GuacamoleClientTimeoutException with the given message.
*
* @param message A human readable description of the exception that
* occurred.
*/
public GuacamoleClientTimeoutException(String message) {
super(message);
}
/**
* Creates a new GuacamoleClientTimeoutException with the given cause.
*
* @param cause The cause of this exception.
*/
public GuacamoleClientTimeoutException(Throwable cause) {
super(cause);
}
@Override
public GuacamoleStatus getStatus() {
return GuacamoleStatus.CLIENT_TIMEOUT;
}
}

View File

@@ -0,0 +1,67 @@
/*
* 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;
import org.apache.guacamole.protocol.GuacamoleStatus;
/**
* An exception which is thrown when too many requests have been received
* by the current client, and further requests are being rejected, either
* temporarily or permanently.
*/
public class GuacamoleClientTooManyException extends GuacamoleClientException {
/**
* Creates a new GuacamoleClientTooManyException with the given message and cause.
*
* @param message A human readable description of the exception that
* occurred.
* @param cause The cause of this exception.
*/
public GuacamoleClientTooManyException(String message, Throwable cause) {
super(message, cause);
}
/**
* Creates a new GuacamoleClientTooManyException with the given message.
*
* @param message A human readable description of the exception that
* occurred.
*/
public GuacamoleClientTooManyException(String message) {
super(message);
}
/**
* Creates a new GuacamoleClientTooManyException with the given cause.
*
* @param cause The cause of this exception.
*/
public GuacamoleClientTooManyException(Throwable cause) {
super(cause);
}
@Override
public GuacamoleStatus getStatus() {
return GuacamoleStatus.CLIENT_TOO_MANY;
}
}

View File

@@ -0,0 +1,67 @@
/*
* 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;
import org.apache.guacamole.protocol.GuacamoleStatus;
/**
* An exception which is thrown when an operation cannot be performed because
* its corresponding connection is closed.
*/
public class GuacamoleConnectionClosedException extends GuacamoleServerException {
/**
* Creates a new GuacamoleConnectionClosedException with the given message
* and cause.
*
* @param message A human readable description of the exception that
* occurred.
* @param cause The cause of this exception.
*/
public GuacamoleConnectionClosedException(String message, Throwable cause) {
super(message, cause);
}
/**
* Creates a new GuacamoleConnectionClosedException with the given message.
*
* @param message A human readable description of the exception that
* occurred.
*/
public GuacamoleConnectionClosedException(String message) {
super(message);
}
/**
* Creates a new GuacamoleConnectionClosedException with the given cause.
*
* @param cause The cause of this exception.
*/
public GuacamoleConnectionClosedException(Throwable cause) {
super(cause);
}
@Override
public GuacamoleStatus getStatus() {
return GuacamoleStatus.SERVER_ERROR;
}
}

View File

@@ -0,0 +1,96 @@
/*
* 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;
import org.apache.guacamole.protocol.GuacamoleStatus;
/**
* A generic exception thrown when parts of the Guacamole API encounter
* errors.
*/
public class GuacamoleException extends Exception {
/**
* Creates a new GuacamoleException with the given message and cause.
*
* @param message A human readable description of the exception that
* occurred.
* @param cause The cause of this exception.
*/
public GuacamoleException(String message, Throwable cause) {
super(message, cause);
}
/**
* Creates a new GuacamoleException with the given message.
*
* @param message A human readable description of the exception that
* occurred.
*/
public GuacamoleException(String message) {
super(message);
}
/**
* Creates a new GuacamoleException with the given cause.
*
* @param cause The cause of this exception.
*/
public GuacamoleException(Throwable cause) {
super(cause);
}
/**
* Returns the Guacamole status associated with this exception. This status
* can then be easily translated into an HTTP error code or Guacamole
* protocol error code.
*
* @return The corresponding Guacamole status.
*/
public GuacamoleStatus getStatus() {
return GuacamoleStatus.SERVER_ERROR;
}
/**
* Returns the most applicable HTTP status code that can be associated
* with this exception.
*
* @return
* An integer representing the most applicable HTTP status code
* associated with this exception.
*/
public int getHttpStatusCode() {
return getStatus().getHttpStatusCode();
}
/**
* Returns the most applicable WebSocket status code that can be
* associated with this exception.
*
* @return
* An integer representing the most applicable WebSocket status
* code associated with this exception.
*/
public int getWebSocketCode() {
return getStatus().getWebSocketCode();
}
}

View File

@@ -0,0 +1,69 @@
/*
* 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;
import org.apache.guacamole.protocol.GuacamoleStatus;
/**
* An exception which is thrown when a resource is no longer available because
* it is closed.
*/
public class GuacamoleResourceClosedException extends GuacamoleClientException {
/**
* Creates a new GuacamoleResourceClosedException with the given message
* and cause.
*
* @param message
* A human readable description of the exception that occurred.
*
* @param cause
* The cause of this exception.
*/
public GuacamoleResourceClosedException(String message, Throwable cause) {
super(message, cause);
}
/**
* Creates a new GuacamoleResourceClosedException with the given message.
*
* @param message
* A human readable description of the exception that occurred.
*/
public GuacamoleResourceClosedException(String message) {
super(message);
}
/**
* Creates a new GuacamoleResourceClosedException with the given cause.
*
* @param cause
* The cause of this exception.
*/
public GuacamoleResourceClosedException(Throwable cause) {
super(cause);
}
@Override
public GuacamoleStatus getStatus() {
return GuacamoleStatus.RESOURCE_CLOSED;
}
}

View File

@@ -0,0 +1,67 @@
/*
* 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;
import org.apache.guacamole.protocol.GuacamoleStatus;
/**
* An exception which is thrown when a resource has been requested, but that
* resource is locked or currently in use, and cannot be accessed by the
* current user.
*/
public class GuacamoleResourceConflictException extends GuacamoleClientException {
/**
* Creates a new GuacamoleResourceConflictException with the given message and cause.
*
* @param message A human readable description of the exception that
* occurred.
* @param cause The cause of this exception.
*/
public GuacamoleResourceConflictException(String message, Throwable cause) {
super(message, cause);
}
/**
* Creates a new GuacamoleResourceConflictException with the given message.
*
* @param message A human readable description of the exception that
* occurred.
*/
public GuacamoleResourceConflictException(String message) {
super(message);
}
/**
* Creates a new GuacamoleResourceConflictException with the given cause.
*
* @param cause The cause of this exception.
*/
public GuacamoleResourceConflictException(Throwable cause) {
super(cause);
}
@Override
public GuacamoleStatus getStatus() {
return GuacamoleStatus.RESOURCE_CONFLICT;
}
}

View File

@@ -0,0 +1,67 @@
/*
* 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;
import org.apache.guacamole.protocol.GuacamoleStatus;
/**
* A generic exception thrown when part of the Guacamole API fails to find
* a requested resource, such as a configuration or tunnel.
*/
public class GuacamoleResourceNotFoundException extends GuacamoleClientException {
/**
* Creates a new GuacamoleResourceNotFoundException with the given message
* and cause.
*
* @param message A human readable description of the exception that
* occurred.
* @param cause The cause of this exception.
*/
public GuacamoleResourceNotFoundException(String message, Throwable cause) {
super(message, cause);
}
/**
* Creates a new GuacamoleResourceNotFoundException with the given message.
*
* @param message A human readable description of the exception that
* occurred.
*/
public GuacamoleResourceNotFoundException(String message) {
super(message);
}
/**
* Creates a new GuacamoleResourceNotFoundException with the given cause.
*
* @param cause The cause of this exception.
*/
public GuacamoleResourceNotFoundException(Throwable cause) {
super(cause);
}
@Override
public GuacamoleStatus getStatus() {
return GuacamoleStatus.RESOURCE_NOT_FOUND;
}
}

View File

@@ -0,0 +1,66 @@
/*
* 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;
import org.apache.guacamole.protocol.GuacamoleStatus;
/**
* A security-related exception thrown when parts of the Guacamole API is
* denying access to a resource.
*/
public class GuacamoleSecurityException extends GuacamoleClientException {
/**
* Creates a new GuacamoleSecurityException with the given message and cause.
*
* @param message A human readable description of the exception that
* occurred.
* @param cause The cause of this exception.
*/
public GuacamoleSecurityException(String message, Throwable cause) {
super(message, cause);
}
/**
* Creates a new GuacamoleSecurityException with the given message.
*
* @param message A human readable description of the exception that
* occurred.
*/
public GuacamoleSecurityException(String message) {
super(message);
}
/**
* Creates a new GuacamoleSecurityException with the given cause.
*
* @param cause The cause of this exception.
*/
public GuacamoleSecurityException(Throwable cause) {
super(cause);
}
@Override
public GuacamoleStatus getStatus() {
return GuacamoleStatus.CLIENT_FORBIDDEN;
}
}

View File

@@ -0,0 +1,66 @@
/*
* 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;
import org.apache.guacamole.protocol.GuacamoleStatus;
/**
* An exception which is thrown when the server is too busy to service the
* request.
*/
public class GuacamoleServerBusyException extends GuacamoleServerException {
/**
* Creates a new GuacamoleServerBusyException with the given message and cause.
*
* @param message A human readable description of the exception that
* occurred.
* @param cause The cause of this exception.
*/
public GuacamoleServerBusyException(String message, Throwable cause) {
super(message, cause);
}
/**
* Creates a new GuacamoleServerBusyException with the given message.
*
* @param message A human readable description of the exception that
* occurred.
*/
public GuacamoleServerBusyException(String message) {
super(message);
}
/**
* Creates a new GuacamoleServerBusyException with the given cause.
*
* @param cause The cause of this exception.
*/
public GuacamoleServerBusyException(Throwable cause) {
super(cause);
}
@Override
public GuacamoleStatus getStatus() {
return GuacamoleStatus.SERVER_BUSY;
}
}

View File

@@ -0,0 +1,67 @@
/*
* 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;
import org.apache.guacamole.protocol.GuacamoleStatus;
/**
* A generic exception thrown when part of the Guacamole API encounters
* an unexpected, internal error. An internal error, if correctable, would
* require correction on the server side, not the client.
*/
public class GuacamoleServerException extends GuacamoleException {
/**
* Creates a new GuacamoleServerException with the given message and cause.
*
* @param message A human readable description of the exception that
* occurred.
* @param cause The cause of this exception.
*/
public GuacamoleServerException(String message, Throwable cause) {
super(message, cause);
}
/**
* Creates a new GuacamoleServerException with the given message.
*
* @param message A human readable description of the exception that
* occurred.
*/
public GuacamoleServerException(String message) {
super(message);
}
/**
* Creates a new GuacamoleServerException with the given cause.
*
* @param cause The cause of this exception.
*/
public GuacamoleServerException(Throwable cause) {
super(cause);
}
@Override
public GuacamoleStatus getStatus() {
return GuacamoleStatus.SERVER_ERROR;
}
}

View File

@@ -0,0 +1,69 @@
/*
* 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;
import org.apache.guacamole.protocol.GuacamoleStatus;
/**
* An exception which indicates that a session within an upstream server (such
* as the remote desktop) has been forcibly terminated.
*/
public class GuacamoleSessionClosedException extends GuacamoleUpstreamException {
/**
* Creates a new GuacamoleSessionClosedException with the given message and
* cause.
*
* @param message
* A human readable description of the exception that occurred.
*
* @param cause
* The cause of this exception.
*/
public GuacamoleSessionClosedException(String message, Throwable cause) {
super(message, cause);
}
/**
* Creates a new GuacamoleSessionClosedException with the given message.
*
* @param message
* A human readable description of the exception that occurred.
*/
public GuacamoleSessionClosedException(String message) {
super(message);
}
/**
* Creates a new GuacamoleSessionClosedException with the given cause.
*
* @param cause
* The cause of this exception.
*/
public GuacamoleSessionClosedException(Throwable cause) {
super(cause);
}
@Override
public GuacamoleStatus getStatus() {
return GuacamoleStatus.SESSION_CLOSED;
}
}

View File

@@ -0,0 +1,69 @@
/*
* 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;
import org.apache.guacamole.protocol.GuacamoleStatus;
/**
* An exception which indicates that a session within an upstream server (such
* as the remote desktop) has ended because it conflicted with another session.
*/
public class GuacamoleSessionConflictException extends GuacamoleUpstreamException {
/**
* Creates a new GuacamoleSessionConflictException with the given message
* and cause.
*
* @param message
* A human readable description of the exception that occurred.
*
* @param cause
* The cause of this exception.
*/
public GuacamoleSessionConflictException(String message, Throwable cause) {
super(message, cause);
}
/**
* Creates a new GuacamoleSessionConflictException with the given message.
*
* @param message
* A human readable description of the exception that occurred.
*/
public GuacamoleSessionConflictException(String message) {
super(message);
}
/**
* Creates a new GuacamoleSessionConflictException with the given cause.
*
* @param cause
* The cause of this exception.
*/
public GuacamoleSessionConflictException(Throwable cause) {
super(cause);
}
@Override
public GuacamoleStatus getStatus() {
return GuacamoleStatus.SESSION_CONFLICT;
}
}

View File

@@ -0,0 +1,69 @@
/*
* 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;
import org.apache.guacamole.protocol.GuacamoleStatus;
/**
* An exception which indicates that a session within an upstream server (such
* as the remote desktop) has ended because it appeared to be inactive.
*/
public class GuacamoleSessionTimeoutException extends GuacamoleUpstreamException {
/**
* Creates a new GuacamoleSessionTimeoutException with the given message
* and cause.
*
* @param message
* A human readable description of the exception that occurred.
*
* @param cause
* The cause of this exception.
*/
public GuacamoleSessionTimeoutException(String message, Throwable cause) {
super(message, cause);
}
/**
* Creates a new GuacamoleSessionTimeoutException with the given message.
*
* @param message
* A human readable description of the exception that occurred.
*/
public GuacamoleSessionTimeoutException(String message) {
super(message);
}
/**
* Creates a new GuacamoleSessionTimeoutException with the given cause.
*
* @param cause
* The cause of this exception.
*/
public GuacamoleSessionTimeoutException(Throwable cause) {
super(cause);
}
@Override
public GuacamoleStatus getStatus() {
return GuacamoleStatus.SESSION_TIMEOUT;
}
}

View File

@@ -0,0 +1,67 @@
/*
* 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;
import org.apache.guacamole.protocol.GuacamoleStatus;
/**
* A security-related exception thrown when parts of the Guacamole API is
* denying access to a resource, but access MAY be granted were the user
* authorized (logged in).
*/
public class GuacamoleUnauthorizedException extends GuacamoleSecurityException {
/**
* Creates a new GuacamoleUnauthorizedException with the given message and cause.
*
* @param message A human readable description of the exception that
* occurred.
* @param cause The cause of this exception.
*/
public GuacamoleUnauthorizedException(String message, Throwable cause) {
super(message, cause);
}
/**
* Creates a new GuacamoleUnauthorizedException with the given message.
*
* @param message A human readable description of the exception that
* occurred.
*/
public GuacamoleUnauthorizedException(String message) {
super(message);
}
/**
* Creates a new GuacamoleUnauthorizedException with the given cause.
*
* @param cause The cause of this exception.
*/
public GuacamoleUnauthorizedException(Throwable cause) {
super(cause);
}
@Override
public GuacamoleStatus getStatus() {
return GuacamoleStatus.CLIENT_UNAUTHORIZED;
}
}

View File

@@ -0,0 +1,66 @@
/*
* 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;
import org.apache.guacamole.protocol.GuacamoleStatus;
/**
* An exception which is thrown when the requested operation is unsupported
* or unimplemented.
*/
public class GuacamoleUnsupportedException extends GuacamoleServerException {
/**
* Creates a new GuacamoleUnsupportedException with the given message and cause.
*
* @param message A human readable description of the exception that
* occurred.
* @param cause The cause of this exception.
*/
public GuacamoleUnsupportedException(String message, Throwable cause) {
super(message, cause);
}
/**
* Creates a new GuacamoleUnsupportedException with the given message.
*
* @param message A human readable description of the exception that
* occurred.
*/
public GuacamoleUnsupportedException(String message) {
super(message);
}
/**
* Creates a new GuacamoleUnsupportedException with the given cause.
*
* @param cause The cause of this exception.
*/
public GuacamoleUnsupportedException(Throwable cause) {
super(cause);
}
@Override
public GuacamoleStatus getStatus() {
return GuacamoleStatus.UNSUPPORTED;
}
}

View File

@@ -0,0 +1,67 @@
/*
* 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;
import org.apache.guacamole.protocol.GuacamoleStatus;
/**
* An exception which indicates than an upstream server (such as the remote
* desktop) is returning an error or is otherwise unreachable.
*/
public class GuacamoleUpstreamException extends GuacamoleException {
/**
* Creates a new GuacamoleUpstreamException with the given message and
* cause.
*
* @param message A human readable description of the exception that
* occurred.
* @param cause The cause of this exception.
*/
public GuacamoleUpstreamException(String message, Throwable cause) {
super(message, cause);
}
/**
* Creates a new GuacamoleUpstreamException with the given message.
*
* @param message A human readable description of the exception that
* occurred.
*/
public GuacamoleUpstreamException(String message) {
super(message);
}
/**
* Creates a new GuacamoleUpstreamException with the given cause.
*
* @param cause The cause of this exception.
*/
public GuacamoleUpstreamException(Throwable cause) {
super(cause);
}
@Override
public GuacamoleStatus getStatus() {
return GuacamoleStatus.UPSTREAM_ERROR;
}
}

View File

@@ -0,0 +1,69 @@
/*
* 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;
import org.apache.guacamole.protocol.GuacamoleStatus;
/**
* An exception which indicates that an upstream server (such as the remote
* desktop) does not appear to exist.
*/
public class GuacamoleUpstreamNotFoundException extends GuacamoleUpstreamException {
/**
* Creates a new GuacamoleUpstreamNotFoundException with the given message
* and cause.
*
* @param message
* A human readable description of the exception that occurred.
*
* @param cause
* The cause of this exception.
*/
public GuacamoleUpstreamNotFoundException(String message, Throwable cause) {
super(message, cause);
}
/**
* Creates a new GuacamoleUpstreamNotFoundException with the given message.
*
* @param message
* A human readable description of the exception that occurred.
*/
public GuacamoleUpstreamNotFoundException(String message) {
super(message);
}
/**
* Creates a new GuacamoleUpstreamNotFoundException with the given cause.
*
* @param cause
* The cause of this exception.
*/
public GuacamoleUpstreamNotFoundException(Throwable cause) {
super(cause);
}
@Override
public GuacamoleStatus getStatus() {
return GuacamoleStatus.UPSTREAM_NOT_FOUND;
}
}

View File

@@ -0,0 +1,67 @@
/*
* 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;
import org.apache.guacamole.protocol.GuacamoleStatus;
/**
* An exception which indicates than an upstream server (such as the remote
* desktop) is taking too long to respond.
*/
public class GuacamoleUpstreamTimeoutException extends GuacamoleUpstreamException {
/**
* Creates a new GuacamoleUpstreamException with the given message and
* cause.
*
* @param message A human readable description of the exception that
* occurred.
* @param cause The cause of this exception.
*/
public GuacamoleUpstreamTimeoutException(String message, Throwable cause) {
super(message, cause);
}
/**
* Creates a new GuacamoleUpstreamException with the given message.
*
* @param message A human readable description of the exception that
* occurred.
*/
public GuacamoleUpstreamTimeoutException(String message) {
super(message);
}
/**
* Creates a new GuacamoleUpstreamException with the given cause.
*
* @param cause The cause of this exception.
*/
public GuacamoleUpstreamTimeoutException(Throwable cause) {
super(cause);
}
@Override
public GuacamoleStatus getStatus() {
return GuacamoleStatus.UPSTREAM_TIMEOUT;
}
}

View File

@@ -0,0 +1,71 @@
/*
* 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;
import org.apache.guacamole.protocol.GuacamoleStatus;
/**
* An exception which indicates that an upstream server (such as the remote
* desktop) is not available to service the request.
*/
public class GuacamoleUpstreamUnavailableException extends GuacamoleUpstreamException {
/**
* Creates a new GuacamoleUpstreamUnavailableException with the given
* message and cause.
*
* @param message
* A human readable description of the exception that occurred.
*
* @param cause
* The cause of this exception.
*/
public GuacamoleUpstreamUnavailableException(String message, Throwable cause) {
super(message, cause);
}
/**
* Creates a new GuacamoleUpstreamUnavailableException with the given
* message.
*
* @param message
* A human readable description of the exception that occurred.
*/
public GuacamoleUpstreamUnavailableException(String message) {
super(message);
}
/**
* Creates a new GuacamoleUpstreamUnavailableException with the given
* cause.
*
* @param cause
* The cause of this exception.
*/
public GuacamoleUpstreamUnavailableException(Throwable cause) {
super(cause);
}
@Override
public GuacamoleStatus getStatus() {
return GuacamoleStatus.UPSTREAM_UNAVAILABLE;
}
}

View File

@@ -0,0 +1,70 @@
/*
* 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.io;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.protocol.GuacamoleInstruction;
/**
* Provides abstract and raw character read access to a stream of Guacamole
* instructions.
*/
public interface GuacamoleReader {
/**
* Returns whether instruction data is available for reading. Note that
* this does not guarantee an entire instruction is available. If a full
* instruction is not available, this function can return true, and a call
* to read() will still block.
*
* @return true if instruction data is available for reading, false
* otherwise.
* @throws GuacamoleException If an error occurs while checking for
* available data.
*/
public boolean available() throws GuacamoleException;
/**
* Reads at least one complete Guacamole instruction, returning a buffer
* containing one or more complete Guacamole instructions and no
* incomplete Guacamole instructions. This function will block until at
* least one complete instruction is available.
*
* @return A buffer containing at least one complete Guacamole instruction,
* or null if no more instructions are available for reading.
* @throws GuacamoleException If an error occurs while reading from the
* stream.
*/
public char[] read() throws GuacamoleException;
/**
* Reads exactly one complete Guacamole instruction and returns the fully
* parsed instruction.
*
* @return The next complete instruction from the stream, fully parsed, or
* null if no more instructions are available for reading.
* @throws GuacamoleException If an error occurs while reading from the
* stream, or if the instruction cannot be
* parsed.
*/
public GuacamoleInstruction readInstruction() throws GuacamoleException;
}

View File

@@ -0,0 +1,66 @@
/*
* 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.io;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.protocol.GuacamoleInstruction;
/**
* Provides abstract and raw character write access to a stream of Guacamole
* instructions.
*/
public interface GuacamoleWriter {
/**
* Writes a portion of the given array of characters to the Guacamole
* instruction stream. The portion must contain only complete Guacamole
* instructions.
*
* @param chunk An array of characters containing Guacamole instructions.
* @param off The start offset of the portion of the array to write.
* @param len The length of the portion of the array to write.
* @throws GuacamoleException If an error occurred while writing the
* portion of the array specified.
*/
public void write(char[] chunk, int off, int len) throws GuacamoleException;
/**
* Writes the entire given array of characters to the Guacamole instruction
* stream. The array must consist only of complete Guacamole instructions.
*
* @param chunk An array of characters consisting only of complete
* Guacamole instructions.
* @throws GuacamoleException If an error occurred while writing the
* the specified array.
*/
public void write(char[] chunk) throws GuacamoleException;
/**
* Writes the given fully parsed instruction to the Guacamole instruction
* stream.
*
* @param instruction The Guacamole instruction to write.
* @throws GuacamoleException If an error occurred while writing the
* instruction.
*/
public void writeInstruction(GuacamoleInstruction instruction) throws GuacamoleException;
}

View File

@@ -0,0 +1,145 @@
/*
* 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.io;
import java.io.IOException;
import java.io.Reader;
import java.net.SocketException;
import java.net.SocketTimeoutException;
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.
*/
private Reader input;
/**
* Creates a new ReaderGuacamoleReader which will use the given Reader as
* the Guacamole instruction stream.
*
* @param input The Reader to use as the Guacamole instruction stream.
*/
public ReaderGuacamoleReader(Reader input) {
this.input = input;
}
/**
* The location within the received data buffer that parsing should begin
* when more data is read.
*/
private int parseStart = 0;
/**
* The buffer holding all received data.
*/
private char[] buffer = new char[20480];
/**
* The number of characters currently used within the data buffer. All
* other characters within the buffer are free space available for
* future reads.
*/
private int usedLength = 0;
@Override
public boolean available() throws GuacamoleException {
try {
return input.ready() || usedLength > parseStart || parser.hasNext();
}
catch (IOException e) {
throw new GuacamoleServerException(e);
}
}
@Override
public char[] read() throws GuacamoleException {
GuacamoleInstruction instruction = readInstruction();
if (instruction == null)
return null;
return instruction.toCharArray();
}
@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;
}
// 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;
}
// Read more instruction data into the buffer
int numRead = input.read(buffer, usedLength, buffer.length - usedLength);
if (numRead == -1)
break;
usedLength += numRead;
}
}
return parser.next();
}
catch (SocketTimeoutException e) {
throw new GuacamoleUpstreamTimeoutException("Connection to guacd timed out.", e);
}
catch (SocketException e) {
throw new GuacamoleConnectionClosedException("Connection to guacd is closed.", e);
}
catch (IOException e) {
throw new GuacamoleServerException(e);
}
}
}

View File

@@ -0,0 +1,81 @@
/*
* 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.io;
import java.io.IOException;
import java.io.Writer;
import java.net.SocketException;
import java.net.SocketTimeoutException;
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;
/**
* A GuacamoleWriter which wraps a standard Java Writer, using that Writer as
* the Guacamole instruction stream.
*/
public class WriterGuacamoleWriter implements GuacamoleWriter {
/**
* Wrapped Writer to be used for all output.
*/
private Writer output;
/**
* Creates a new WriterGuacamoleWriter which will use the given Writer as
* the Guacamole instruction stream.
*
* @param output The Writer to use as the Guacamole instruction stream.
*/
public WriterGuacamoleWriter(Writer output) {
this.output = output;
}
@Override
public void write(char[] chunk, int off, int len) throws GuacamoleException {
try {
output.write(chunk, off, len);
output.flush();
}
catch (SocketTimeoutException e) {
throw new GuacamoleUpstreamTimeoutException("Connection to guacd timed out.", e);
}
catch (SocketException e) {
throw new GuacamoleConnectionClosedException("Connection to guacd is closed.", e);
}
catch (IOException e) {
throw new GuacamoleServerException(e);
}
}
@Override
public void write(char[] chunk) throws GuacamoleException {
write(chunk, 0, chunk.length);
}
@Override
public void writeInstruction(GuacamoleInstruction instruction) throws GuacamoleException {
write(instruction.toString().toCharArray());
}
}

View File

@@ -0,0 +1,24 @@
/*
* 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.
*/
/**
* All classes relating directly to data input or output.
*/
package org.apache.guacamole.io;

View File

@@ -0,0 +1,128 @@
/*
* 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.net;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.io.GuacamoleReader;
import org.apache.guacamole.io.GuacamoleWriter;
/**
* Base GuacamoleTunnel implementation which synchronizes access to the
* underlying reader and writer with reentrant locks. Implementations need only
* provide the tunnel's UUID and socket.
*/
public abstract class AbstractGuacamoleTunnel implements GuacamoleTunnel {
/**
* Lock acquired when a read operation is in progress.
*/
private final ReentrantLock readerLock;
/**
* Lock acquired when a write operation is in progress.
*/
private final ReentrantLock writerLock;
/**
* Creates a new GuacamoleTunnel which synchronizes access to the
* Guacamole instruction stream associated with the underlying
* GuacamoleSocket.
*/
public AbstractGuacamoleTunnel() {
readerLock = new ReentrantLock();
writerLock = new ReentrantLock();
}
/**
* Acquires exclusive read access to the Guacamole instruction stream
* and returns a GuacamoleReader for reading from that stream.
*
* @return A GuacamoleReader for reading from the Guacamole instruction
* stream.
*/
@Override
public GuacamoleReader acquireReader() {
readerLock.lock();
return getSocket().getReader();
}
/**
* Relinquishes exclusive read access to the Guacamole instruction
* stream. This function should be called whenever a thread finishes using
* a GuacamoleTunnel's GuacamoleReader.
*/
@Override
public void releaseReader() {
readerLock.unlock();
}
/**
* Returns whether there are threads waiting for read access to the
* Guacamole instruction stream.
*
* @return true if threads are waiting for read access the Guacamole
* instruction stream, false otherwise.
*/
@Override
public boolean hasQueuedReaderThreads() {
return readerLock.hasQueuedThreads();
}
/**
* Acquires exclusive write access to the Guacamole instruction stream
* and returns a GuacamoleWriter for writing to that stream.
*
* @return A GuacamoleWriter for writing to the Guacamole instruction
* stream.
*/
@Override
public GuacamoleWriter acquireWriter() {
writerLock.lock();
return getSocket().getWriter();
}
/**
* Relinquishes exclusive write access to the Guacamole instruction
* stream. This function should be called whenever a thread finishes using
* a GuacamoleTunnel's GuacamoleWriter.
*/
@Override
public void releaseWriter() {
writerLock.unlock();
}
@Override
public boolean hasQueuedWriterThreads() {
return writerLock.hasQueuedThreads();
}
@Override
public void close() throws GuacamoleException {
getSocket().close();
}
@Override
public boolean isOpen() {
return getSocket().isOpen();
}
}

View File

@@ -0,0 +1,84 @@
/*
* 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.net;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.io.GuacamoleReader;
import org.apache.guacamole.io.GuacamoleWriter;
/**
* GuacamoleSocket implementation which simply delegates all function calls to
* an underlying GuacamoleSocket.
*/
public class DelegatingGuacamoleSocket implements GuacamoleSocket {
/**
* The wrapped socket.
*/
private final GuacamoleSocket socket;
/**
* Wraps the given GuacamoleSocket such that all function calls against
* this DelegatingGuacamoleSocket will be delegated to it.
*
* @param socket
* The GuacamoleSocket to wrap.
*/
public DelegatingGuacamoleSocket(GuacamoleSocket socket) {
this.socket = socket;
}
/**
* Returns the underlying GuacamoleSocket wrapped by this
* DelegatingGuacamoleSocket.
*
* @return
* The GuacamoleSocket wrapped by this DelegatingGuacamoleSocket.
*/
protected GuacamoleSocket getDelegateSocket() {
return socket;
}
@Override
public String getProtocol() {
return socket.getProtocol();
}
@Override
public GuacamoleReader getReader() {
return socket.getReader();
}
@Override
public GuacamoleWriter getWriter() {
return socket.getWriter();
}
@Override
public void close() throws GuacamoleException {
socket.close();
}
@Override
public boolean isOpen() {
return socket.isOpen();
}
}

View File

@@ -0,0 +1,99 @@
/*
* 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.net;
import java.util.UUID;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.io.GuacamoleReader;
import org.apache.guacamole.io.GuacamoleWriter;
/**
* GuacamoleTunnel implementation which simply delegates all function calls to
* an underlying GuacamoleTunnel.
*/
public class DelegatingGuacamoleTunnel implements GuacamoleTunnel {
/**
* The wrapped GuacamoleTunnel.
*/
private final GuacamoleTunnel tunnel;
/**
* Wraps the given tunnel such that all function calls against this tunnel
* will be delegated to it.
*
* @param tunnel
* The GuacamoleTunnel to wrap.
*/
public DelegatingGuacamoleTunnel(GuacamoleTunnel tunnel) {
this.tunnel = tunnel;
}
@Override
public GuacamoleReader acquireReader() {
return tunnel.acquireReader();
}
@Override
public void releaseReader() {
tunnel.releaseReader();
}
@Override
public boolean hasQueuedReaderThreads() {
return tunnel.hasQueuedReaderThreads();
}
@Override
public GuacamoleWriter acquireWriter() {
return tunnel.acquireWriter();
}
@Override
public void releaseWriter() {
tunnel.releaseWriter();
}
@Override
public boolean hasQueuedWriterThreads() {
return tunnel.hasQueuedWriterThreads();
}
@Override
public UUID getUUID() {
return tunnel.getUUID();
}
@Override
public GuacamoleSocket getSocket() {
return tunnel.getSocket();
}
@Override
public void close() throws GuacamoleException {
tunnel.close();
}
@Override
public boolean isOpen() {
return tunnel.isOpen();
}
}

View File

@@ -0,0 +1,86 @@
/*
* 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.net;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.io.GuacamoleReader;
import org.apache.guacamole.io.GuacamoleWriter;
/**
* Provides abstract socket-like access to a Guacamole connection.
*/
public interface GuacamoleSocket {
/**
* Returns the name of the protocol to be used. If the protocol is not
* known or the implementation refuses to reveal the underlying protocol,
* null is returned.
*
* <p>Implementations <strong>should</strong> aim to expose the name of the
* underlying protocol, such that protocol-specific responses like the
* "required" and "argv" instructions can be handled correctly by code
* consuming the GuacamoleSocket.
*
* @return
* The name of the protocol to be used, or null if this information is
* not available.
*/
public default String getProtocol() {
return null;
}
/**
* Returns a GuacamoleReader which can be used to read from the
* Guacamole instruction stream associated with the connection
* represented by this GuacamoleSocket.
*
* @return A GuacamoleReader which can be used to read from the
* Guacamole instruction stream.
*/
public GuacamoleReader getReader();
/**
* Returns a GuacamoleWriter which can be used to write to the
* Guacamole instruction stream associated with the connection
* represented by this GuacamoleSocket.
*
* @return A GuacamoleWriter which can be used to write to the
* Guacamole instruction stream.
*/
public GuacamoleWriter getWriter();
/**
* Releases all resources in use by the connection represented by this
* GuacamoleSocket.
*
* @throws GuacamoleException If an error occurs while releasing resources.
*/
public void close() throws GuacamoleException;
/**
* Returns whether this GuacamoleSocket is open and can be used for reading
* and writing.
*
* @return true if this GuacamoleSocket is open, false otherwise.
*/
public boolean isOpen();
}

View File

@@ -0,0 +1,124 @@
/*
* 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.net;
import java.util.UUID;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.io.GuacamoleReader;
import org.apache.guacamole.io.GuacamoleWriter;
/**
* Provides a unique identifier and synchronized access to the GuacamoleReader
* and GuacamoleWriter associated with a GuacamoleSocket.
*/
public interface GuacamoleTunnel {
/**
* The Guacamole protocol instruction opcode reserved for arbitrary
* internal use by tunnel implementations. The value of this opcode is
* guaranteed to be the empty string (""). Tunnel implementations may use
* this opcode for any purpose. It is currently used by the HTTP tunnel to
* mark the end of the HTTP response, and by the WebSocket tunnel to
* transmit the tunnel UUID.
*/
static final String INTERNAL_DATA_OPCODE = "";
/**
* Acquires exclusive read access to the Guacamole instruction stream
* and returns a GuacamoleReader for reading from that stream.
*
* @return A GuacamoleReader for reading from the Guacamole instruction
* stream.
*/
GuacamoleReader acquireReader();
/**
* Relinquishes exclusive read access to the Guacamole instruction
* stream. This function should be called whenever a thread finishes using
* a GuacamoleTunnel's GuacamoleReader.
*/
void releaseReader();
/**
* Returns whether there are threads waiting for read access to the
* Guacamole instruction stream.
*
* @return true if threads are waiting for read access the Guacamole
* instruction stream, false otherwise.
*/
boolean hasQueuedReaderThreads();
/**
* Acquires exclusive write access to the Guacamole instruction stream
* and returns a GuacamoleWriter for writing to that stream.
*
* @return A GuacamoleWriter for writing to the Guacamole instruction
* stream.
*/
GuacamoleWriter acquireWriter();
/**
* Relinquishes exclusive write access to the Guacamole instruction
* stream. This function should be called whenever a thread finishes using
* a GuacamoleTunnel's GuacamoleWriter.
*/
void releaseWriter();
/**
* Returns whether there are threads waiting for write access to the
* Guacamole instruction stream.
*
* @return true if threads are waiting for write access the Guacamole
* instruction stream, false otherwise.
*/
boolean hasQueuedWriterThreads();
/**
* Returns the unique identifier associated with this GuacamoleTunnel.
*
* @return The unique identifier associated with this GuacamoleTunnel.
*/
UUID getUUID();
/**
* Returns the GuacamoleSocket used by this GuacamoleTunnel for reading
* and writing.
*
* @return The GuacamoleSocket used by this GuacamoleTunnel.
*/
GuacamoleSocket getSocket();
/**
* Release all resources allocated to this GuacamoleTunnel.
*
* @throws GuacamoleException if an error occurs while releasing
* resources.
*/
void close() throws GuacamoleException;
/**
* Returns whether this GuacamoleTunnel is open, or has been closed.
*
* @return true if this GuacamoleTunnel is open, false if it is closed.
*/
boolean isOpen();
}

View File

@@ -0,0 +1,150 @@
/*
* 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.net;
import org.apache.guacamole.io.GuacamoleReader;
import org.apache.guacamole.io.ReaderGuacamoleReader;
import org.apache.guacamole.io.WriterGuacamoleWriter;
import org.apache.guacamole.io.GuacamoleWriter;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketTimeoutException;
import java.net.StandardSocketOptions;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleServerException;
import org.apache.guacamole.GuacamoleUpstreamTimeoutException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Provides abstract socket-like access to a Guacamole connection over a given
* hostname and port.
*/
public class InetGuacamoleSocket implements GuacamoleSocket {
/**
* Logger for this class.
*/
private Logger logger = LoggerFactory.getLogger(InetGuacamoleSocket.class);
/**
* The GuacamoleReader this socket should read from.
*/
private GuacamoleReader reader;
/**
* The GuacamoleWriter this socket should write to.
*/
private GuacamoleWriter writer;
/**
* The number of milliseconds to wait for data on the TCP socket before
* timing out.
*/
private static final int SOCKET_TIMEOUT = 15000;
/**
* The TCP socket that the GuacamoleReader and GuacamoleWriter exposed
* by this class should affect.
*/
private Socket sock;
/**
* Creates a new InetGuacamoleSocket which reads and writes instructions
* to the Guacamole instruction stream of the Guacamole proxy server
* running at the given hostname and port.
*
* @param hostname The hostname of the Guacamole proxy server to connect to.
* @param port The port of the Guacamole proxy server to connect to.
* @throws GuacamoleException If an error occurs while connecting to the
* Guacamole proxy server.
*/
public InetGuacamoleSocket(String hostname, int port) throws GuacamoleException {
try {
logger.debug("Connecting to guacd at {}:{}.", hostname, port);
// Get address
SocketAddress address = new InetSocketAddress(
InetAddress.getByName(hostname),
port
);
// Connect with timeout
sock = new Socket();
sock.connect(address, SOCKET_TIMEOUT);
// Set read timeout
sock.setSoTimeout(SOCKET_TIMEOUT);
// Set TCP_NODELAY to avoid any latency that would otherwise be
// added by the networking stack and Nagle's algorithm
sock.setTcpNoDelay(true);
// On successful connect, retrieve I/O streams
reader = new ReaderGuacamoleReader(new InputStreamReader(sock.getInputStream(), "UTF-8"));
writer = new WriterGuacamoleWriter(new OutputStreamWriter(sock.getOutputStream(), "UTF-8"));
}
catch (SocketTimeoutException e) {
throw new GuacamoleUpstreamTimeoutException("Connection timed out.", e);
}
catch (IOException e) {
throw new GuacamoleServerException(e);
}
}
@Override
public void close() throws GuacamoleException {
try {
logger.debug("Closing socket to guacd.");
sock.close();
}
catch (IOException e) {
throw new GuacamoleServerException(e);
}
}
@Override
public GuacamoleReader getReader() {
return reader;
}
@Override
public GuacamoleWriter getWriter() {
return writer;
}
@Override
public boolean isOpen() {
return !sock.isClosed();
}
}

View File

@@ -0,0 +1,144 @@
/*
* 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.net;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleServerException;
import org.apache.guacamole.io.GuacamoleReader;
import org.apache.guacamole.io.GuacamoleWriter;
import org.apache.guacamole.io.ReaderGuacamoleReader;
import org.apache.guacamole.io.WriterGuacamoleWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Provides abstract socket-like access to a Guacamole connection over SSL to
* a given hostname and port.
*/
public class SSLGuacamoleSocket implements GuacamoleSocket {
/**
* Logger for this class.
*/
private Logger logger = LoggerFactory.getLogger(SSLGuacamoleSocket.class);
/**
* The GuacamoleReader this socket should read from.
*/
private GuacamoleReader reader;
/**
* The GuacamoleWriter this socket should write to.
*/
private GuacamoleWriter writer;
/**
* The number of milliseconds to wait for data on the TCP socket before
* timing out.
*/
private static final int SOCKET_TIMEOUT = 15000;
/**
* The TCP socket that the GuacamoleReader and GuacamoleWriter exposed
* by this class should affect.
*/
private Socket sock;
/**
* Creates a new SSLGuacamoleSocket which reads and writes instructions
* to the Guacamole instruction stream of the Guacamole proxy server
* running at the given hostname and port using SSL.
*
* @param hostname The hostname of the Guacamole proxy server to connect to.
* @param port The port of the Guacamole proxy server to connect to.
* @throws GuacamoleException If an error occurs while connecting to the
* Guacamole proxy server.
*/
public SSLGuacamoleSocket(String hostname, int port) throws GuacamoleException {
// Get factory for SSL sockets
SocketFactory socket_factory = SSLSocketFactory.getDefault();
try {
logger.debug("Connecting to guacd at {}:{} via SSL/TLS.",
hostname, port);
// Get address
SocketAddress address = new InetSocketAddress(
InetAddress.getByName(hostname),
port
);
// Connect with timeout
sock = socket_factory.createSocket();
sock.connect(address, SOCKET_TIMEOUT);
// Set read timeout
sock.setSoTimeout(SOCKET_TIMEOUT);
// On successful connect, retrieve I/O streams
reader = new ReaderGuacamoleReader(new InputStreamReader(sock.getInputStream(), "UTF-8"));
writer = new WriterGuacamoleWriter(new OutputStreamWriter(sock.getOutputStream(), "UTF-8"));
}
catch (IOException e) {
throw new GuacamoleServerException(e);
}
}
@Override
public void close() throws GuacamoleException {
try {
logger.debug("Closing socket to guacd.");
sock.close();
}
catch (IOException e) {
throw new GuacamoleServerException(e);
}
}
@Override
public GuacamoleReader getReader() {
return reader;
}
@Override
public GuacamoleWriter getWriter() {
return writer;
}
@Override
public boolean isOpen() {
return !sock.isClosed();
}
}

View File

@@ -0,0 +1,64 @@
/*
* 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.net;
import java.util.UUID;
/**
* GuacamoleTunnel implementation which uses a provided socket. The UUID of
* the tunnel will be randomly generated.
*/
public class SimpleGuacamoleTunnel extends AbstractGuacamoleTunnel {
/**
* The UUID associated with this tunnel. Every tunnel must have a
* corresponding UUID such that tunnel read/write requests can be
* directed to the proper tunnel.
*/
private final UUID uuid = UUID.randomUUID();
/**
* The GuacamoleSocket that tunnel should use for communication on
* behalf of the connecting user.
*/
private final GuacamoleSocket socket;
/**
* Creates a new GuacamoleTunnel which synchronizes access to the
* Guacamole instruction stream associated with the given GuacamoleSocket.
*
* @param socket The GuacamoleSocket to provide synchronized access for.
*/
public SimpleGuacamoleTunnel(GuacamoleSocket socket) {
this.socket = socket;
}
@Override
public UUID getUUID() {
return uuid;
}
@Override
public GuacamoleSocket getSocket() {
return socket;
}
}

View File

@@ -0,0 +1,25 @@
/*
* 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.
*/
/**
* Classes which apply to network-specific concepts, such as low-level sockets
* and tunnels.
*/
package org.apache.guacamole.net;

View File

@@ -0,0 +1,26 @@
/*
* 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.
*/
/**
* All classes which apply generally across the Guacamole web application
* and all other web applications which use the API provided by the
* Guacamole project.
*/
package org.apache.guacamole;

View File

@@ -0,0 +1,361 @@
/*
* 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.List;
import org.apache.guacamole.GuacamoleConnectionClosedException;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleServerException;
import org.apache.guacamole.io.GuacamoleReader;
import org.apache.guacamole.io.GuacamoleWriter;
import org.apache.guacamole.net.DelegatingGuacamoleSocket;
import org.apache.guacamole.net.GuacamoleSocket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A GuacamoleSocket which pre-configures the connection based on a given
* GuacamoleConfiguration, completing the initial protocol handshake before
* accepting data for read or write.
*
* This is useful for forcing a connection to the Guacamole proxy server with
* a specific configuration while disallowing the client that will be using
* this GuacamoleSocket from manually controlling the initial protocol
* handshake.
*/
public class ConfiguredGuacamoleSocket extends DelegatingGuacamoleSocket {
/**
* Logger for this class.
*/
private static final Logger logger =
LoggerFactory.getLogger(ConfiguredGuacamoleSocket.class);
/**
* The configuration to use when performing the Guacamole protocol
* handshake.
*/
private GuacamoleConfiguration config;
/**
* The unique identifier associated with this connection, as determined
* by the "ready" instruction received from the Guacamole proxy.
*/
private String id;
/**
* The protocol version that will be used to communicate with guacd. The
* default is 1.0.0, and, if the server does not provide a specific version
* it will be assumed that it operates at this version and certain features
* may be unavailable.
*/
private GuacamoleProtocolVersion protocolVersion =
GuacamoleProtocolVersion.VERSION_1_0_0;
/**
* Parses the given "error" instruction, throwing a GuacamoleException that
* corresponds to its status code and message.
*
* @param instruction
* The "error" instruction to parse.
*
* @throws GuacamoleException
* A GuacamoleException that corresponds to the status code and message
* present within the given "error" instruction.
*/
private static void handleReceivedError(GuacamoleInstruction instruction)
throws GuacamoleException {
// Provide reasonable default error message for invalid "error"
// instructions that fail to provide one
String message = "Internal error within guacd / protocol handling.";
// Consider all error instructions without a corresponding status code
// to be server errors
GuacamoleStatus status = GuacamoleStatus.SERVER_ERROR;
// Parse human-readable message from "error" instruction, warning if no
// message was given
List<String> args = instruction.getArgs();
if (args.size() >= 1)
message = args.get(0);
else
logger.debug("Received \"error\" instruction with no corresponding message.");
// Parse the status code from the received error instruction, warning
// if the status code is missing or invalid
if (args.size() >= 2) {
try {
// Translate numeric status code into a GuacamoleStatus
int statusCode = Integer.parseInt(args.get(1));
GuacamoleStatus parsedStatus = GuacamoleStatus.fromGuacamoleStatusCode(statusCode);
if (parsedStatus != null)
status = parsedStatus;
else
logger.debug("Received \"error\" instruction with unknown/invalid status code: {}", statusCode);
}
catch (NumberFormatException e) {
logger.debug("Received \"error\" instruction with non-numeric status code.", e);
}
}
else
logger.debug("Received \"error\" instruction without status code.");
// Convert parsed status code and message to a GuacamoleException
throw status.toException(message);
}
/**
* Waits for the instruction having the given opcode, returning that
* instruction once it has been read. If the instruction is never read,
* an exception is thrown.
*
* Respects server control instructions that are allowed during the handshake
* phase, namely {@code error} and {@code disconnect}.
*
* @param reader
* The reader to read instructions from.
*
* @param opcode
* The opcode of the instruction we are expecting.
*
* @return
* The instruction having the given opcode.
*
* @throws GuacamoleException
* If an error occurs while reading, or if the expected instruction is
* not read.
*/
private GuacamoleInstruction expect(GuacamoleReader reader, String opcode)
throws GuacamoleException {
// Wait for an instruction
GuacamoleInstruction instruction = reader.readInstruction();
if (instruction == null)
throw new GuacamoleServerException("End of stream while waiting for \"" + opcode + "\".");
// Report connection closure if server explicitly disconnects
if ("disconnect".equals(instruction.getOpcode()))
throw new GuacamoleConnectionClosedException("Server disconnected while waiting for \"" + opcode + "\".");
// Pass through any received errors as GuacamoleExceptions
if ("error".equals(instruction.getOpcode()))
handleReceivedError(instruction);
// Ensure instruction has expected opcode
if (!instruction.getOpcode().equals(opcode))
throw new GuacamoleServerException("Expected \"" + opcode + "\" instruction but instead received \"" + instruction.getOpcode() + "\".");
return instruction;
}
/**
* Creates a new ConfiguredGuacamoleSocket which uses the given
* GuacamoleConfiguration to complete the initial protocol handshake over
* the given GuacamoleSocket. A default GuacamoleClientInformation object
* is used to provide basic client information.
*
* @param socket The GuacamoleSocket to wrap.
* @param config The GuacamoleConfiguration to use to complete the initial
* protocol handshake.
* @throws GuacamoleException If an error occurs while completing the
* initial protocol handshake.
*/
public ConfiguredGuacamoleSocket(GuacamoleSocket socket,
GuacamoleConfiguration config) throws GuacamoleException {
this(socket, config, new GuacamoleClientInformation());
}
/**
* Creates a new ConfiguredGuacamoleSocket which uses the given
* GuacamoleConfiguration and GuacamoleClientInformation to complete the
* initial protocol handshake over the given GuacamoleSocket.
*
* @param socket The GuacamoleSocket to wrap.
* @param config The GuacamoleConfiguration to use to complete the initial
* protocol handshake.
* @param info The GuacamoleClientInformation to use to complete the initial
* protocol handshake.
* @throws GuacamoleException If an error occurs while completing the
* initial protocol handshake.
*/
public ConfiguredGuacamoleSocket(GuacamoleSocket socket,
GuacamoleConfiguration config,
GuacamoleClientInformation info) throws GuacamoleException {
super(socket);
this.config = config;
// Get reader and writer
GuacamoleReader reader = socket.getReader();
GuacamoleWriter writer = socket.getWriter();
// Get protocol / connection ID
String select_arg = config.getConnectionID();
if (select_arg == null)
select_arg = config.getProtocol();
// Send requested protocol or connection ID
writer.writeInstruction(new GuacamoleInstruction("select", select_arg));
// Wait for server args
GuacamoleInstruction args = expect(reader, "args");
// Build args list off provided names and config
List<String> arg_names = args.getArgs();
String[] arg_values = new String[arg_names.size()];
for (int i=0; i<arg_names.size(); i++) {
// Retrieve argument name
String arg_name = arg_names.get(i);
// Check for valid protocol version as first argument
if (i == 0) {
GuacamoleProtocolVersion version = GuacamoleProtocolVersion.parseVersion(arg_name);
if (version != null) {
// Use the lowest common version supported
if (version.atLeast(GuacamoleProtocolVersion.LATEST))
version = GuacamoleProtocolVersion.LATEST;
// Respond with the version selected
arg_values[i] = version.toString();
protocolVersion = version;
continue;
}
}
// Get defined value for name
String value = config.getParameter(arg_name);
// If value defined, set that value
if (value != null) arg_values[i] = value;
// Otherwise, leave value blank
else arg_values[i] = "";
}
// Send size
writer.writeInstruction(
new GuacamoleInstruction(
"size",
Integer.toString(info.getOptimalScreenWidth()),
Integer.toString(info.getOptimalScreenHeight()),
Integer.toString(info.getOptimalResolution())
)
);
// Send supported audio formats
writer.writeInstruction(
new GuacamoleInstruction(
"audio",
info.getAudioMimetypes().toArray(new String[0])
));
// Send supported video formats
writer.writeInstruction(
new GuacamoleInstruction(
"video",
info.getVideoMimetypes().toArray(new String[0])
));
// Send supported image formats
writer.writeInstruction(
new GuacamoleInstruction(
"image",
info.getImageMimetypes().toArray(new String[0])
));
// Send client timezone, if supported and available
if (GuacamoleProtocolCapability.TIMEZONE_HANDSHAKE.isSupported(protocolVersion)) {
String timezone = info.getTimezone();
if (timezone != null)
writer.writeInstruction(new GuacamoleInstruction("timezone", timezone));
}
// Send client name, if supported and available
if (GuacamoleProtocolCapability.NAME_HANDSHAKE.isSupported(protocolVersion)) {
String name = info.getName();
if (name != null)
writer.writeInstruction(new GuacamoleInstruction("name", name));
}
// Send args
writer.writeInstruction(new GuacamoleInstruction("connect", arg_values));
// Wait for ready, store ID
GuacamoleInstruction ready = expect(reader, "ready");
List<String> ready_args = ready.getArgs();
if (ready_args.isEmpty())
throw new GuacamoleServerException("No connection ID received");
id = ready.getArgs().get(0);
}
/**
* Returns the GuacamoleConfiguration used to configure this
* ConfiguredGuacamoleSocket.
*
* @return The GuacamoleConfiguration used to configure this
* ConfiguredGuacamoleSocket.
*/
public GuacamoleConfiguration getConfiguration() {
return config;
}
/**
* Returns the unique ID associated with the Guacamole connection
* negotiated by this ConfiguredGuacamoleSocket. The ID is provided by
* the "ready" instruction returned by the Guacamole proxy.
*
* @return The ID of the negotiated Guacamole connection.
*/
public String getConnectionID() {
return id;
}
/**
* Returns the version of the Guacamole protocol associated with the
* Guacamole connection negotiated by this ConfiguredGuacamoleSocket. This
* version is the lowest version common to both ConfiguredGuacamoleSocket
* and the relevant Guacamole proxy instance (guacd).
*
* @return
* The protocol version that this ConfiguredGuacamoleSocket will use to
* communicate with guacd.
*/
public GuacamoleProtocolVersion getProtocolVersion() {
return protocolVersion;
}
@Override
public String getProtocol() {
return getConfiguration().getProtocol();
}
}

View File

@@ -0,0 +1,267 @@
/*
* 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.LinkedList;
import java.util.List;
import java.util.Queue;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleUpstreamException;
import org.apache.guacamole.GuacamoleUpstreamNotFoundException;
import org.apache.guacamole.GuacamoleUpstreamTimeoutException;
import org.apache.guacamole.GuacamoleUpstreamUnavailableException;
import org.apache.guacamole.io.GuacamoleReader;
import org.apache.guacamole.net.DelegatingGuacamoleSocket;
import org.apache.guacamole.net.GuacamoleSocket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* GuacamoleSocket which intercepts errors received early in the Guacamole
* session. Upstream errors which are intercepted early enough result in
* exceptions thrown immediately within the FailoverGuacamoleSocket's
* constructor, allowing a different socket to be substituted prior to
* fulfilling the connection.
*/
public class FailoverGuacamoleSocket extends DelegatingGuacamoleSocket {
/**
* Logger for this class.
*/
private static final Logger logger =
LoggerFactory.getLogger(FailoverGuacamoleSocket.class);
/**
* The default maximum number of characters of Guacamole instruction data
* to store if no explicit limit is provided.
*/
private static final int DEFAULT_INSTRUCTION_QUEUE_LIMIT = 131072;
/**
* Queue of all instructions read while this FailoverGuacamoleSocket was
* being constructed.
*/
private final Queue<GuacamoleInstruction> instructionQueue =
new LinkedList<GuacamoleInstruction>();
/**
* Parses the given "error" instruction, throwing an exception if the
* instruction represents an error from the upstream remote desktop.
*
* @param instruction
* The "error" instruction to parse.
*
* @throws GuacamoleUpstreamException
* If the "error" instruction represents an error from the upstream
* remote desktop.
*/
private static void handleUpstreamErrors(GuacamoleInstruction instruction)
throws GuacamoleUpstreamException {
// Ignore error instructions which are missing the status code
List<String> args = instruction.getArgs();
if (args.size() < 2) {
logger.debug("Received \"error\" instruction without status code.");
return;
}
// Parse the status code from the received error instruction
int statusCode;
try {
statusCode = Integer.parseInt(args.get(1));
}
catch (NumberFormatException e) {
logger.debug("Received \"error\" instruction with non-numeric status code.", e);
return;
}
// Translate numeric status code into a GuacamoleStatus
GuacamoleStatus status = GuacamoleStatus.fromGuacamoleStatusCode(statusCode);
if (status == null) {
logger.debug("Received \"error\" instruction with unknown/invalid status code: {}", statusCode);
return;
}
// Only handle error instructions related to the upstream remote desktop
switch (status) {
// Generic upstream error
case UPSTREAM_ERROR:
throw new GuacamoleUpstreamException(args.get(0));
// Upstream is unreachable
case UPSTREAM_NOT_FOUND:
throw new GuacamoleUpstreamNotFoundException(args.get(0));
// Upstream did not respond
case UPSTREAM_TIMEOUT:
throw new GuacamoleUpstreamTimeoutException(args.get(0));
// Upstream is refusing the connection
case UPSTREAM_UNAVAILABLE:
throw new GuacamoleUpstreamUnavailableException(args.get(0));
}
}
/**
* Creates a new FailoverGuacamoleSocket which reads Guacamole instructions
* from the given socket, searching for errors from the upstream remote
* desktop until the given instruction queue limit is reached. If an
* upstream error is encountered, it is thrown as a
* GuacamoleUpstreamException. This constructor will block until an error
* is encountered, until insufficient space remains in the instruction
* queue, or until the connection appears to have been successful.
* Once the FailoverGuacamoleSocket has been created, all reads, writes,
* etc. will be delegated to the provided socket.
*
* @param socket
* The GuacamoleSocket of the Guacamole connection this
* FailoverGuacamoleSocket should handle.
*
* @param instructionQueueLimit
* The maximum number of characters of Guacamole instruction data to
* store within the instruction queue while searching for errors. Once
* this limit is exceeded, the connection is assumed to be successful.
*
* @throws GuacamoleException
* If an error occurs while reading data from the provided socket.
*
* @throws GuacamoleUpstreamException
* If the connection to guacd succeeded, but an error occurred while
* connecting to the remote desktop.
*/
public FailoverGuacamoleSocket(GuacamoleSocket socket,
final int instructionQueueLimit)
throws GuacamoleException, GuacamoleUpstreamException {
super(socket);
int totalQueueSize = 0;
GuacamoleInstruction instruction;
GuacamoleReader reader = socket.getReader();
// Continuously read instructions, searching for errors
while ((instruction = reader.readInstruction()) != null) {
// Add instruction to tail of instruction queue
instructionQueue.add(instruction);
// If instruction is a "sync" instruction, stop reading
String opcode = instruction.getOpcode();
if (opcode.equals("sync"))
break;
// If instruction is an "error" instruction, parse its contents and
// stop reading
if (opcode.equals("error")) {
handleUpstreamErrors(instruction);
break;
}
// Otherwise, track total data parsed, and assume connection is
// successful if no error encountered within reasonable space
totalQueueSize += instruction.toString().length();
if (totalQueueSize >= instructionQueueLimit)
break;
}
}
/**
* Creates a new FailoverGuacamoleSocket which reads Guacamole instructions
* from the given socket, searching for errors from the upstream remote
* desktop until a maximum of 128KB of instruction data has been queued. If
* an upstream error is encountered, it is thrown as a
* GuacamoleUpstreamException. This constructor will block until an error
* is encountered, until insufficient space remains in the instruction
* queue, or until the connection appears to have been successful.
* Once the FailoverGuacamoleSocket has been created, all reads, writes,
* etc. will be delegated to the provided socket.
*
* @param socket
* The GuacamoleSocket of the Guacamole connection this
* FailoverGuacamoleSocket should handle.
*
* @throws GuacamoleException
* If an error occurs while reading data from the provided socket.
*
* @throws GuacamoleUpstreamException
* If the connection to guacd succeeded, but an error occurred while
* connecting to the remote desktop.
*/
public FailoverGuacamoleSocket(GuacamoleSocket socket)
throws GuacamoleException, GuacamoleUpstreamException {
this(socket, DEFAULT_INSTRUCTION_QUEUE_LIMIT);
}
/**
* GuacamoleReader which reads instructions from the queue populated when
* the FailoverGuacamoleSocket was constructed. Once the queue has been
* emptied, reads are delegated directly to the reader of the wrapped
* socket.
*/
private final GuacamoleReader queuedReader = new GuacamoleReader() {
@Override
public boolean available() throws GuacamoleException {
return !instructionQueue.isEmpty() || getDelegateSocket().getReader().available();
}
@Override
public char[] read() throws GuacamoleException {
// Read instructions from queue before finally delegating to
// underlying reader (received when FailoverGuacamoleSocket was
// being constructed)
if (!instructionQueue.isEmpty()) {
GuacamoleInstruction instruction = instructionQueue.remove();
return instruction.toString().toCharArray();
}
return getDelegateSocket().getReader().read();
}
@Override
public GuacamoleInstruction readInstruction()
throws GuacamoleException {
// Read instructions from queue before finally delegating to
// underlying reader (received when FailoverGuacamoleSocket was
// being constructed)
if (!instructionQueue.isEmpty())
return instructionQueue.remove();
return getDelegateSocket().getReader().readInstruction();
}
};
@Override
public GuacamoleReader getReader() {
return queuedReader;
}
}

View File

@@ -0,0 +1,93 @@
/*
* 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 org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.io.GuacamoleReader;
/**
* GuacamoleReader which applies a given GuacamoleFilter to observe or alter all
* read instructions. Instructions may also be dropped or denied by the filter.
*/
public class FilteredGuacamoleReader implements GuacamoleReader {
/**
* The wrapped GuacamoleReader.
*/
private final GuacamoleReader reader;
/**
* The filter to apply when reading instructions.
*/
private final GuacamoleFilter filter;
/**
* Wraps the given GuacamoleReader, applying the given filter to all read
* instructions. Future reads will return only instructions which pass
* the filter.
*
* @param reader The GuacamoleReader to wrap.
* @param filter The filter which dictates which instructions are read, and
* how.
*/
public FilteredGuacamoleReader(GuacamoleReader reader, GuacamoleFilter filter) {
this.reader = reader;
this.filter = filter;
}
@Override
public boolean available() throws GuacamoleException {
return reader.available();
}
@Override
public char[] read() throws GuacamoleException {
GuacamoleInstruction filteredInstruction = readInstruction();
if (filteredInstruction == null)
return null;
return filteredInstruction.toString().toCharArray();
}
@Override
public GuacamoleInstruction readInstruction() throws GuacamoleException {
GuacamoleInstruction filteredInstruction;
// Read and filter instructions until no instructions are dropped
do {
// Read next instruction
GuacamoleInstruction unfilteredInstruction = reader.readInstruction();
if (unfilteredInstruction == null)
return null;
// Apply filter
filteredInstruction = filter.filter(unfilteredInstruction);
} while (filteredInstruction == null);
return filteredInstruction;
}
}

View File

@@ -0,0 +1,83 @@
/*
* 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 org.apache.guacamole.io.GuacamoleReader;
import org.apache.guacamole.io.GuacamoleWriter;
import org.apache.guacamole.net.DelegatingGuacamoleSocket;
import org.apache.guacamole.net.GuacamoleSocket;
/**
* Implementation of GuacamoleSocket which allows individual instructions to be
* intercepted, overridden, etc.
*/
public class FilteredGuacamoleSocket extends DelegatingGuacamoleSocket {
/**
* A reader for the wrapped GuacamoleSocket which may be filtered.
*/
private final GuacamoleReader reader;
/**
* A writer for the wrapped GuacamoleSocket which may be filtered.
*/
private final GuacamoleWriter writer;
/**
* Creates a new FilteredGuacamoleSocket which uses the given filters to
* determine whether instructions read/written are allowed through,
* modified, etc. If reads or writes should be unfiltered, simply specify
* null rather than a particular filter.
*
* @param socket The GuacamoleSocket to wrap.
* @param readFilter The GuacamoleFilter to apply to all read instructions,
* if any.
* @param writeFilter The GuacamoleFilter to apply to all written
* instructions, if any.
*/
public FilteredGuacamoleSocket(GuacamoleSocket socket, GuacamoleFilter readFilter, GuacamoleFilter writeFilter) {
super(socket);
// Apply filter to reader
if (readFilter != null)
reader = new FilteredGuacamoleReader(socket.getReader(), readFilter);
else
reader = socket.getReader();
// Apply filter to writer
if (writeFilter != null)
writer = new FilteredGuacamoleWriter(socket.getWriter(), writeFilter);
else
writer = socket.getWriter();
}
@Override
public GuacamoleReader getReader() {
return reader;
}
@Override
public GuacamoleWriter getWriter() {
return writer;
}
}

View File

@@ -0,0 +1,102 @@
/*
* 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 org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleServerException;
import org.apache.guacamole.io.GuacamoleWriter;
/**
* GuacamoleWriter which applies a given GuacamoleFilter to observe or alter
* all written instructions. Instructions may also be dropped or denied by
* the filter.
*/
public class FilteredGuacamoleWriter implements GuacamoleWriter {
/**
* The wrapped GuacamoleWriter.
*/
private final GuacamoleWriter writer;
/**
* The filter to apply when writing instructions.
*/
private final GuacamoleFilter filter;
/**
* Parser for reading instructions prior to writing, such that they can be
* passed on to the filter.
*/
private final GuacamoleParser parser = new GuacamoleParser();
/**
* Wraps the given GuacamoleWriter, applying the given filter to all written
* instructions. Future writes will only write instructions which pass
* the filter.
*
* @param writer The GuacamoleWriter to wrap.
* @param filter The filter which dictates which instructions are written,
* and how.
*/
public FilteredGuacamoleWriter(GuacamoleWriter writer, GuacamoleFilter filter) {
this.writer = writer;
this.filter = filter;
}
@Override
public void write(char[] chunk, int offset, int length) throws GuacamoleException {
// Write all data in chunk
while (length > 0) {
// Pass as much data through the parser as possible
int parsed;
while ((parsed = parser.append(chunk, offset, length)) != 0) {
offset += parsed;
length -= parsed;
}
// If no instruction is available, it must be incomplete
if (!parser.hasNext())
throw new GuacamoleServerException("Filtered write() contained an incomplete instruction.");
// Write single instruction through filter
writeInstruction(parser.next());
}
}
@Override
public void write(char[] chunk) throws GuacamoleException {
write(chunk, 0, chunk.length);
}
@Override
public void writeInstruction(GuacamoleInstruction instruction) throws GuacamoleException {
// Write instruction only if not dropped
GuacamoleInstruction filteredInstruction = filter.filter(instruction);
if (filteredInstruction != null)
writer.writeInstruction(filteredInstruction);
}
}

View File

@@ -0,0 +1,205 @@
/*
* 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.ArrayList;
import java.util.List;
/**
* An abstract representation of Guacamole client information, including all
* information required by the Guacamole protocol during the preamble.
*/
public class GuacamoleClientInformation {
/**
* The optimal screen width requested by the client, in pixels.
*/
private int optimalScreenWidth = 1024;
/**
* The optimal screen height requested by the client, in pixels.
*/
private int optimalScreenHeight = 768;
/**
* The resolution of the optimal dimensions given, in DPI.
*/
private int optimalResolution = 96;
/**
* The list of audio mimetypes reported by the client to be supported.
*/
private final List<String> audioMimetypes = new ArrayList<>();
/**
* The list of video mimetypes reported by the client to be supported.
*/
private final List<String> videoMimetypes = new ArrayList<>();
/**
* The list of image mimetypes reported by the client to be supported.
*/
private final List<String> imageMimetypes = new ArrayList<>();
/**
* The name of the user reported by the client.
*/
private String name;
/**
* The timezone reported by the client.
*/
private String timezone;
/**
* Returns the optimal screen width requested by the client, in pixels.
* @return The optimal screen width requested by the client, in pixels.
*/
public int getOptimalScreenWidth() {
return optimalScreenWidth;
}
/**
* Sets the client's optimal screen width.
* @param optimalScreenWidth The optimal screen width of the client.
*/
public void setOptimalScreenWidth(int optimalScreenWidth) {
this.optimalScreenWidth = optimalScreenWidth;
}
/**
* Returns the optimal screen height requested by the client, in pixels.
* @return The optimal screen height requested by the client, in pixels.
*/
public int getOptimalScreenHeight() {
return optimalScreenHeight;
}
/**
* Sets the client's optimal screen height.
* @param optimalScreenHeight The optimal screen height of the client.
*/
public void setOptimalScreenHeight(int optimalScreenHeight) {
this.optimalScreenHeight = optimalScreenHeight;
}
/**
* Returns the resolution of the screen if the optimal width and height are
* used, in DPI.
*
* @return The optimal screen resolution.
*/
public int getOptimalResolution() {
return optimalResolution;
}
/**
* Sets the resolution of the screen if the optimal width and height are
* used, in DPI.
*
* @param optimalResolution The optimal screen resolution in DPI.
*/
public void setOptimalResolution(int optimalResolution) {
this.optimalResolution = optimalResolution;
}
/**
* Returns the list of audio mimetypes supported by the client. To add or
* removed supported mimetypes, the list returned by this function can be
* modified.
*
* @return The set of audio mimetypes supported by the client.
*/
public List<String> getAudioMimetypes() {
return audioMimetypes;
}
/**
* Returns the list of video mimetypes supported by the client. To add or
* removed supported mimetypes, the list returned by this function can be
* modified.
*
* @return The set of video mimetypes supported by the client.
*/
public List<String> getVideoMimetypes() {
return videoMimetypes;
}
/**
* Returns the list of image mimetypes supported by the client. To add or
* removed supported mimetypes, the list returned by this function can be
* modified.
*
* @return
* The set of image mimetypes supported by the client.
*/
public List<String> getImageMimetypes() {
return imageMimetypes;
}
/**
* Returns the name of the Guacamole user as reported by the client, or null
* if the user name is not set.
*
* @return
* A string value of the human-readable name reported by the client.
*/
public String getName() {
return name;
}
/**
* Return the timezone as reported by the client, or null if the timezone
* is not set. Valid timezones are specified in IANA zone key format,
* also known as Olson time zone database or TZ Database.
*
* @return
* A string value of the timezone reported by the client.
*/
public String getTimezone() {
return timezone;
}
/**
* Set the human-readable name of the user associated with this client.
*
* @param name
* The human-readable name of the user associated with this client.
*/
public void setName(String name) {
this.name = name;
}
/**
* Set the string value of the timezone, or null if the timezone will not
* be provided by the client. Valid timezones are specified in IANA zone
* key format (aka Olson time zone database or tz database).
*
* @param timezone
* The string value of the timezone reported by the client, in tz
* database format, or null if the timezone is not provided by the
* client.
*/
public void setTimezone(String timezone) {
this.timezone = timezone;
}
}

View File

@@ -0,0 +1,205 @@
/*
* 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.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* All information necessary to complete the initial protocol handshake of a
* Guacamole session.
*/
public class GuacamoleConfiguration implements Serializable {
/**
* Identifier unique to this version of GuacamoleConfiguration.
*/
private static final long serialVersionUID = 1L;
/**
* The ID of the connection being joined. If this value is present,
* the protocol need not be specified.
*/
private String connectionID;
/**
* The name of the protocol associated with this configuration.
*/
private String protocol;
/**
* Map of all associated parameter values, indexed by parameter name.
*/
private final Map<String, String> parameters = new HashMap<String, String>();
/**
* Creates a new, blank GuacamoleConfiguration with its protocol, connection
* ID, and parameters unset.
*/
public GuacamoleConfiguration() {
}
/**
* Copies the given GuacamoleConfiguration, creating a new, indepedent
* GuacamoleConfiguration containing the same protocol, connection ID,
* and parameter values, if any.
*
* @param config The GuacamoleConfiguration to copy.
*/
public GuacamoleConfiguration(GuacamoleConfiguration config) {
// Copy protocol and connection ID
protocol = config.getProtocol();
connectionID = config.getConnectionID();
// Copy parameter values
for (String name : config.getParameterNames())
parameters.put(name, config.getParameter(name));
}
/**
* Returns the ID of the connection being joined, if any. If no connection
* is being joined, this returns null, and the protocol must be set.
*
* @return The ID of the connection being joined, or null if no connection
* is being joined.
*/
public String getConnectionID() {
return connectionID;
}
/**
* Sets the ID of the connection being joined, if any. If no connection
* is being joined, this value must be omitted.
*
* @param connectionID The ID of the connection being joined.
*/
public void setConnectionID(String connectionID) {
this.connectionID = connectionID;
}
/**
* Returns the name of the protocol to be used.
*
* @return
* The name of the protocol to be used.
*/
public String getProtocol() {
return protocol;
}
/**
* Sets the name of the protocol to be used. If no connection is being
* joined (a new connection is being established), this value must be set.
*
* <p>If a connection is being joined, <strong>this value should still be
* set</strong> to ensure that protocol-specific responses like the
* "required" and "argv" instructions can be understood in their proper
* context by other code that may consume this GuacamoleConfiguration like
* {@link ConfiguredGuacamoleSocket}.
*
* <p>If this value is unavailable or remains unset, it is still possible
* to join an established connection using
* {@link #setConnectionID(java.lang.String)}, however protocol-specific
* responses like the "required" and "argv" instructions might not be
* possible to handle correctly if the underlying protocol is not made
* available through some other means to the client receiving those
* responses.
*
* @param protocol
* The name of the protocol to be used.
*/
public void setProtocol(String protocol) {
this.protocol = protocol;
}
/**
* Returns the value set for the parameter with the given name, if any.
* @param name The name of the parameter to return the value for.
* @return The value of the parameter with the given name, or null if
* that parameter has not been set.
*/
public String getParameter(String name) {
return parameters.get(name);
}
/**
* Sets the value for the parameter with the given name.
*
* @param name The name of the parameter to set the value for.
* @param value The value to set for the parameter with the given name.
*/
public void setParameter(String name, String value) {
parameters.put(name, value);
}
/**
* Removes the value set for the parameter with the given name.
*
* @param name The name of the parameter to remove the value of.
*/
public void unsetParameter(String name) {
parameters.remove(name);
}
/**
* Returns a set of all currently defined parameter names. Each name
* corresponds to a parameter that has a value set on this
* GuacamoleConfiguration via setParameter().
*
* @return A set of all currently defined parameter names.
*/
public Set<String> getParameterNames() {
return Collections.unmodifiableSet(parameters.keySet());
}
/**
* Returns a map which contains parameter name/value pairs as key/value
* pairs. Changes to this map will affect the parameters stored within
* this configuration.
*
* @return
* A map which contains all parameter name/value pairs as key/value
* pairs.
*/
public Map<String, String> getParameters() {
return parameters;
}
/**
* Replaces all current parameters with the parameters defined within the
* given map. Key/value pairs within the map represent parameter name/value
* pairs.
*
* @param parameters
* A map which contains all parameter name/value pairs as key/value
* pairs.
*/
public void setParameters(Map<String, String> parameters) {
this.parameters.clear();
this.parameters.putAll(parameters);
}
}

View File

@@ -0,0 +1,46 @@
/*
* 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 org.apache.guacamole.GuacamoleException;
/**
* Interface which provides for the filtering of individual instructions. Each
* filtered instruction may be allowed through untouched, modified, replaced,
* dropped, or explicitly denied.
*/
public interface GuacamoleFilter {
/**
* Applies the filter to the given instruction, returning the original
* instruction, a modified version of the original, or null, depending
* on the implementation.
*
* @param instruction The instruction to filter.
* @return The original instruction, if the instruction is to be allowed,
* a modified version of the instruction, if the instruction is
* to be overridden, or null, if the instruction is to be dropped.
* @throws GuacamoleException If an error occurs filtering the instruction,
* or if the instruction must be explicitly
* denied.
*/
public GuacamoleInstruction filter(GuacamoleInstruction instruction) throws GuacamoleException;
}

View File

@@ -0,0 +1,203 @@
/*
* 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.Collections;
import java.util.List;
/**
* An abstract representation of a Guacamole instruction, as defined by the
* Guacamole protocol.
*/
public class GuacamoleInstruction {
/**
* The opcode of this instruction.
*/
private final String opcode;
/**
* All arguments of this instruction, in order.
*/
private final List<String> args;
/**
* The cached String result of converting this GuacamoleInstruction to the
* format used by the Guacamole protocol.
*
* @see #toString()
*/
private String rawString = null;
/**
* The cached char[] result of converting this GuacamoleInstruction to the
* format used by the Guacamole protocol.
*
* @see #toCharArray()
*/
private char[] rawChars = null;
/**
* Creates a new GuacamoleInstruction having the given opcode and list of
* argument values.
*
* @param opcode
* The opcode of the instruction to create.
*
* @param args
* The list of argument values to provide in the new instruction, if
* any.
*/
public GuacamoleInstruction(String opcode, String... args) {
this.opcode = opcode;
this.args = Collections.unmodifiableList(Arrays.asList(args));
}
/**
* Creates a new GuacamoleInstruction having the given opcode and list of
* argument values. The list given will be used to back the internal list of
* arguments and the list returned by {@link #getArgs()}.
* <p>
* The provided argument list may not be modified in any way after being
* provided to this constructor. Doing otherwise will result in undefined
* behavior.
*
* @param opcode
* The opcode of the instruction to create.
*
* @param args
* The list of argument values to provide in the new instruction, if
* any.
*/
public GuacamoleInstruction(String opcode, List<String> args) {
this.opcode = opcode;
this.args = Collections.unmodifiableList(args);
}
/**
* Returns the opcode associated with this GuacamoleInstruction.
*
* @return
* The opcode associated with this GuacamoleInstruction.
*/
public String getOpcode() {
return opcode;
}
/**
* Returns a List of all argument values specified for this
* GuacamoleInstruction. Note that the List returned is immutable.
* Attempts to modify the list will result in exceptions.
*
* @return
* An unmodifiable List of all argument values specified for this
* GuacamoleInstruction.
*/
public List<String> getArgs() {
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.
*
* @return
* This GuacamoleInstruction in the form it would be sent over the
* Guacamole protocol.
*/
@Override
public String toString() {
// Avoid rebuilding Guacamole protocol form of instruction if already
// known
if (rawString == null) {
// Prefer to construct String from existing char array, rather than
// reconstruct protocol from scratch
if (rawChars != null)
rawString = new String(rawChars);
// Reconstruct protocol details only if truly necessary
else {
StringBuilder buff = new StringBuilder();
// Write opcode
appendElement(buff, opcode);
// Write argument values
for (String value : args) {
buff.append(',');
appendElement(buff, value);
}
// Write terminator
buff.append(';');
// Cache result for future calls
rawString = buff.toString();
}
}
return rawString;
}
/**
* Returns this GuacamoleInstruction in the form it would be sent over the
* Guacamole protocol. The returned char[] MUST NOT be modified. If the
* returned char[] is modified, the results of doing so are undefined.
*
* @return
* This GuacamoleInstruction in the form it would be sent over the
* Guacamole protocol. The returned char[] MUST NOT be modified.
*/
public char[] toCharArray() {
// Avoid rebuilding Guacamole protocol form of instruction if already
// known
if (rawChars == null)
rawChars = toString().toCharArray();
return rawChars;
}
}

View File

@@ -0,0 +1,412 @@
/*
* 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.
*/
public class GuacamoleParser implements Iterator<GuacamoleInstruction> {
/**
* 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 = 128;
/**
* 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, 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.
*/
private int elementCount = 0;
/**
* All currently parsed elements.
*/
private final String elements[] = new String[INSTRUCTION_MAX_ELEMENTS];
/**
* A copy of the raw protocol data that has been parsed for the current
* instruction. This value is maintained by {@link #append(char[], int, int)}.
*/
private final char rawInstruction[] = new char[INSTRUCTION_MAX_LENGTH];
/**
* The offset within {@link #rawInstruction} that new data should be
* appended. This value is maintained by {@link #append(char[], int, int)}.
*/
private int rawInstructionOffset = 0;
/**
* GuacamoleInstruction that efficiently exposes the originally parsed
* character buffer for calls to {@link #toString()} and {@link #toCharArray()}
* rather than regenerate the buffer from scratch.
*/
private static class ParsedGuacamoleInstruction extends GuacamoleInstruction {
/**
* The original data parsed to produce this GuacamoleInstruction.
*/
private final char[] rawChars;
/**
* A String containing the original data parsed to produce this
* GuacamoleInstruction.
*/
private String rawString = null;
/**
* Creates a new GuacamoleInstruction that efficiently exposes the
* originally parsed character buffer rather than regenerating that
* buffer from scratch for {@link #toString()} and {@link #toCharArray()}.
*
* @param opcode
* The opcode of the instruction to create.
*
* @param args
* The list of argument values to provide in the new instruction, if
* any.
*
* @param raw
* The underlying representation of this instruction as would be sent
* over the network via the Guacamole protocol.
*/
public ParsedGuacamoleInstruction(String opcode, List<String> args, char[] raw) {
super(opcode, args);
this.rawChars = raw;
}
@Override
public String toString() {
if (rawString == null)
rawString = new String(rawChars);
return rawString;
}
@Override
public char[] toCharArray() {
return rawChars;
}
}
/**
* 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 originalOffset = offset;
int originalLength = length;
// Process as much of the received chunk as possible
while (length > 0) {
int appended = processElement(chunk, offset, length);
if (appended == 0)
break;
length -= appended;
offset += appended;
}
// Update the raw copy of the received instruction with whatever data
// has now been processed
int charsParsed = originalLength - length;
if (charsParsed > 0) {
System.arraycopy(chunk, originalOffset, rawInstruction, rawInstructionOffset, charsParsed);
rawInstructionOffset += charsParsed;
// If the instruction is now complete, we're good to store the
// parsed instruction for future retrieval via next()
if (state == State.COMPLETE) {
parsedInstruction = new ParsedGuacamoleInstruction(elements[0],
Arrays.asList(elements).subList(1, elementCount),
Arrays.copyOf(rawInstruction, rawInstructionOffset));
rawInstructionOffset = 0;
}
}
return charsParsed;
}
/**
* Processes additional data from the given buffer, potentially adding
* another element to the current instruction being parsed. This function
* will need to be invoked multiple times per instruction until all data
* for that instruction is ready.
* <p>
* This function DOES NOT update {@link #parsedInstruction}. The caller
* ({@link #append(char[], int, int)}) must update this as necessary when
* the parser {@link #state} indicates the instruction is complete.
*
* @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.
*/
private int processElement(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
elementCodepoints = elementLength = parsedLength;
} // end parse length
// Parse element content, if available
while (state == State.PARSING_CONTENT && charsParsed + elementLength + 1 <= length) {
// 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;
// Add element to currently parsed elements
elements[elementCount++] = element;
// Read terminator char following element
char terminator = chunk[offset + charsParsed++];
switch (terminator) {
// If semicolon, store end-of-instruction
case ';':
state = State.COMPLETE;
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 ','");
}
} // 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().");
}
}

View File

@@ -0,0 +1,109 @@
/*
* 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;
/**
* Capabilities which may not be present in all versions of the Guacamole
* protocol.
*/
public enum GuacamoleProtocolCapability {
/**
* The protocol does not require handshake instructions to be sent in a
* specific order, nor that all handshake instructions be sent. Arbitrary
* handshake order was introduced in
* {@link GuacamoleProtocolVersion#VERSION_1_1_0}.
*/
ARBITRARY_HANDSHAKE_ORDER(GuacamoleProtocolVersion.VERSION_1_1_0),
/**
* Support for the "msg" instruction. The "msg" instruction allows the
* server to send messages to the client. Support for this instruction was
* introduced in {@link GuacamoleProtocolVersion#VERSION_1_5_0}.
*/
MSG_INSTRUCTION(GuacamoleProtocolVersion.VERSION_1_5_0),
/**
* Support for the "name" handshake instruction, allowing clients to send
* the name of the Guacamole user to be passed to guacd and associated with
* connections. Support for this instruction was introduced in
* {@link GuacamoleProtocolVersion#VERSION_1_5_0}.
*/
NAME_HANDSHAKE(GuacamoleProtocolVersion.VERSION_1_5_0),
/**
* Negotiation of Guacamole protocol version between client and server
* during the protocol handshake. The ability to negotiate protocol
* versions was introduced in
* {@link GuacamoleProtocolVersion#VERSION_1_1_0}.
*/
PROTOCOL_VERSION_DETECTION(GuacamoleProtocolVersion.VERSION_1_1_0),
/**
* Support for the "required" instruction. The "required" instruction
* allows the server to explicitly request connection parameters from the
* client without which the connection cannot continue, such as user
* credentials. Support for this instruction was introduced in
* {@link GuacamoleProtocolVersion#VERSION_1_3_0}.
*/
REQUIRED_INSTRUCTION(GuacamoleProtocolVersion.VERSION_1_3_0),
/**
* Support for the "timezone" handshake instruction. The "timezone"
* instruction allows the client to request that the server forward their
* local timezone for use within the remote desktop session. Support for
* forwarding the client timezone was introduced in
* {@link GuacamoleProtocolVersion#VERSION_1_1_0}.
*/
TIMEZONE_HANDSHAKE(GuacamoleProtocolVersion.VERSION_1_1_0);
/**
* The minimum protocol version required to support this capability.
*/
private final GuacamoleProtocolVersion version;
/**
* Create a new enum value with the given protocol version as the minimum
* required to support the capability.
*
* @param version
* The minimum required protocol version for supporting the
* capability.
*/
private GuacamoleProtocolCapability(GuacamoleProtocolVersion version) {
this.version = version;
}
/**
* Returns whether this capability is supported in the given Guacamole
* protocol version.
*
* @param version
* The Guacamole protocol version to check.
*
* @return
* true if this capability is supported by the given protocol version,
* false otherwise.
*/
public boolean isSupported(GuacamoleProtocolVersion version) {
return version.atLeast(this.version);
}
}

View File

@@ -0,0 +1,227 @@
/*
* 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.regex.Matcher;
import java.util.regex.Pattern;
/**
* Representation of a Guacamole protocol version. Convenience methods are
* provided for parsing and comparing versions, as is necessary when
* determining the version of the Guacamole protocol common to guacd and a
* client.
*/
public class GuacamoleProtocolVersion {
/**
* Protocol version 1.0.0 and older. Any client that doesn't explicitly
* set the protocol version will negotiate down to this protocol version.
* This requires that handshake instructions be ordered correctly, and
* lacks support for certain protocol-related features introduced in later
* versions.
*/
public static final GuacamoleProtocolVersion VERSION_1_0_0 = new GuacamoleProtocolVersion(1, 0, 0);
/**
* Protocol version 1.1.0, which introduces Client-Server version
* detection, arbitrary handshake instruction order, and support
* for passing the client timezone to the server during the handshake.
*/
public static final GuacamoleProtocolVersion VERSION_1_1_0 = new GuacamoleProtocolVersion(1, 1, 0);
/**
* Protocol version 1.3.0, which introduces the "required" instruction
* allowing the server to explicitly request connection parameters from the
* client.
*/
public static final GuacamoleProtocolVersion VERSION_1_3_0 = new GuacamoleProtocolVersion(1, 3, 0);
/**
* Protocol version 1.5.0, which introduces the "msg" instruction, allowing
* the server to send message notifications that will be displayed on the
* client. The version also adds support for the "name" handshake
* instruction, allowing guacd to store the name of the user who is
* accessing the connection.
*/
public static final GuacamoleProtocolVersion VERSION_1_5_0 = new GuacamoleProtocolVersion(1, 5, 0);
/**
* The most recent version of the Guacamole protocol at the time this
* version of GuacamoleProtocolVersion was built.
*/
public static final GuacamoleProtocolVersion LATEST = VERSION_1_5_0;
/**
* A regular expression that matches the VERSION_X_Y_Z pattern, where
* X is the major version component, Y is the minor version component,
* and Z is the patch version component. This expression puts each of
* the version components in their own group so that they can be easily
* used later.
*/
private static final Pattern VERSION_PATTERN =
Pattern.compile("^VERSION_([0-9]+)_([0-9]+)_([0-9]+)$");
/**
* The major version component of the protocol version.
*/
private final int major;
/**
* The minor version component of the protocol version.
*/
private final int minor;
/**
* The patch version component of the protocol version.
*/
private final int patch;
/**
* Generate a new GuacamoleProtocolVersion object with the given
* major version, minor version, and patch version.
*
* @param major
* The integer representation of the major version component.
*
* @param minor
* The integer representation of the minor version component.
*
* @param patch
* The integer representation of the patch version component.
*/
public GuacamoleProtocolVersion(int major, int minor, int patch) {
this.major = major;
this.minor = minor;
this.patch = patch;
}
/**
* Return the major version component of the protocol version.
*
* @return
* The integer major version component.
*/
public int getMajor() {
return major;
}
/**
* Return the minor version component of the protocol version.
*
* @return
* The integer minor version component.
*/
public int getMinor() {
return minor;
}
/**
* Return the patch version component of the protocol version.
*
* @return
* The integer patch version component.
*/
public int getPatch() {
return patch;
}
/**
* Returns whether this GuacamoleProtocolVersion is at least as recent as
* (greater than or equal to) the given version.
*
* @param otherVersion
* The version to which this GuacamoleProtocolVersion should be compared.
*
* @return
* true if this object is at least as recent as the given version,
* false if the given version is newer.
*/
public boolean atLeast(GuacamoleProtocolVersion otherVersion) {
// If major is not the same, return inequality
if (major != otherVersion.getMajor())
return this.major > otherVersion.getMajor();
// Major is the same, but minor is not, return minor inequality
if (minor != otherVersion.getMinor())
return this.minor > otherVersion.getMinor();
// Major and minor are equal, so return patch inequality
return patch >= otherVersion.getPatch();
}
/**
* Parse the String format of the version provided and return the
* the enum value matching that version. If no value is provided, return
* null.
*
* @param version
* The String format of the version to parse.
*
* @return
* The enum value that matches the specified version, VERSION_1_0_0
* if no match is found, or null if no comparison version is provided.
*/
public static GuacamoleProtocolVersion parseVersion(String version) {
// Validate format of version string
Matcher versionMatcher = VERSION_PATTERN.matcher(version);
if (!versionMatcher.matches())
return null;
// Parse version number from version string
return new GuacamoleProtocolVersion(
Integer.parseInt(versionMatcher.group(1)),
Integer.parseInt(versionMatcher.group(2)),
Integer.parseInt(versionMatcher.group(3))
);
}
@Override
public int hashCode() {
int hash = 7;
hash = 61 * hash + this.major;
hash = 61 * hash + this.minor;
hash = 61 * hash + this.patch;
return hash;
}
@Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof GuacamoleProtocolVersion))
return false;
// Versions are equal if all major/minor/patch components are identical
final GuacamoleProtocolVersion otherVersion = (GuacamoleProtocolVersion) obj;
return this.major == otherVersion.getMajor()
&& this.minor == otherVersion.getMinor()
&& this.patch == otherVersion.getPatch();
}
@Override
public String toString() {
return "VERSION_" + getMajor() + "_" + getMinor() + "_" + getPatch();
}
}

View File

@@ -0,0 +1,421 @@
/*
* 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 org.apache.guacamole.GuacamoleClientBadTypeException;
import org.apache.guacamole.GuacamoleClientException;
import org.apache.guacamole.GuacamoleClientOverrunException;
import org.apache.guacamole.GuacamoleClientTimeoutException;
import org.apache.guacamole.GuacamoleClientTooManyException;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleResourceClosedException;
import org.apache.guacamole.GuacamoleResourceConflictException;
import org.apache.guacamole.GuacamoleResourceNotFoundException;
import org.apache.guacamole.GuacamoleSecurityException;
import org.apache.guacamole.GuacamoleServerBusyException;
import org.apache.guacamole.GuacamoleServerException;
import org.apache.guacamole.GuacamoleSessionClosedException;
import org.apache.guacamole.GuacamoleSessionConflictException;
import org.apache.guacamole.GuacamoleSessionTimeoutException;
import org.apache.guacamole.GuacamoleUnauthorizedException;
import org.apache.guacamole.GuacamoleUnsupportedException;
import org.apache.guacamole.GuacamoleUpstreamException;
import org.apache.guacamole.GuacamoleUpstreamNotFoundException;
import org.apache.guacamole.GuacamoleUpstreamTimeoutException;
import org.apache.guacamole.GuacamoleUpstreamUnavailableException;
/**
* All possible statuses returned by various Guacamole instructions, each having
* a corresponding code.
*/
public enum GuacamoleStatus {
/**
* The operation succeeded.
*/
SUCCESS(200, 1000, 0x0000) {
@Override
public GuacamoleException toException(String message) {
throw new IllegalStateException("The Guacamole protocol SUCCESS "
+ "status code cannot be converted into a "
+ "GuacamoleException.", new GuacamoleServerException(message));
}
},
/**
* The requested operation is unsupported.
*/
UNSUPPORTED(501, 1011, 0x0100) {
@Override
public GuacamoleException toException(String message) {
return new GuacamoleUnsupportedException(message);
}
},
/**
* The operation could not be performed due to an internal failure.
*/
SERVER_ERROR(500, 1011, 0x0200) {
@Override
public GuacamoleException toException(String message) {
return new GuacamoleServerException(message);
}
},
/**
* The operation could not be performed as the server is busy.
*/
SERVER_BUSY(503, 1008, 0x0201) {
@Override
public GuacamoleException toException(String message) {
return new GuacamoleServerBusyException(message);
}
},
/**
* The operation could not be performed because the upstream server is not
* responding.
*/
UPSTREAM_TIMEOUT(504, 1011, 0x0202) {
@Override
public GuacamoleException toException(String message) {
return new GuacamoleUpstreamTimeoutException(message);
}
},
/**
* The operation was unsuccessful due to an error or otherwise unexpected
* condition of the upstream server.
*/
UPSTREAM_ERROR(502, 1011, 0x0203) {
@Override
public GuacamoleException toException(String message) {
return new GuacamoleUpstreamException(message);
}
},
/**
* The operation could not be performed as the requested resource does not
* exist.
*/
RESOURCE_NOT_FOUND(404, 1002, 0x0204) {
@Override
public GuacamoleException toException(String message) {
return new GuacamoleResourceNotFoundException(message);
}
},
/**
* The operation could not be performed as the requested resource is already
* in use.
*/
RESOURCE_CONFLICT(409, 1008, 0x0205) {
@Override
public GuacamoleException toException(String message) {
return new GuacamoleResourceConflictException(message);
}
},
/**
* The operation could not be performed as the requested resource is now
* closed.
*/
RESOURCE_CLOSED(404, 1002, 0x0206) {
@Override
public GuacamoleException toException(String message) {
return new GuacamoleResourceClosedException(message);
}
},
/**
* The operation could not be performed because the upstream server does
* not appear to exist.
*/
UPSTREAM_NOT_FOUND(502, 1011, 0x0207) {
@Override
public GuacamoleException toException(String message) {
return new GuacamoleUpstreamNotFoundException(message);
}
},
/**
* The operation could not be performed because the upstream server is not
* available to service the request.
*/
UPSTREAM_UNAVAILABLE(502, 1011, 0x0208) {
@Override
public GuacamoleException toException(String message) {
return new GuacamoleUpstreamUnavailableException(message);
}
},
/**
* The session within the upstream server has ended because it conflicted
* with another session.
*/
SESSION_CONFLICT(409, 1008, 0x0209) {
@Override
public GuacamoleException toException(String message) {
return new GuacamoleSessionConflictException(message);
}
},
/**
* The session within the upstream server has ended because it appeared to
* be inactive.
*/
SESSION_TIMEOUT(408, 1002, 0x020A) {
@Override
public GuacamoleException toException(String message) {
return new GuacamoleSessionTimeoutException(message);
}
},
/**
* The session within the upstream server has been forcibly terminated.
*/
SESSION_CLOSED(404, 1002, 0x020B) {
@Override
public GuacamoleException toException(String message) {
return new GuacamoleSessionClosedException(message);
}
},
/**
* The operation could not be performed because bad parameters were given.
*/
CLIENT_BAD_REQUEST(400, 1002, 0x0300) {
@Override
public GuacamoleException toException(String message) {
return new GuacamoleClientException(message);
}
},
/**
* Permission was denied to perform the operation, as the user is not yet
* authorized (not yet logged in, for example). As HTTP 401 has implications
* for HTTP-specific authorization schemes, this status continues to map to
* HTTP 403 ("Forbidden"). To do otherwise would risk unintended effects.
*/
CLIENT_UNAUTHORIZED(403, 1008, 0x0301) {
@Override
public GuacamoleException toException(String message) {
return new GuacamoleUnauthorizedException(message);
}
},
/**
* Permission was denied to perform the operation, and this operation will
* not be granted even if the user is authorized.
*/
CLIENT_FORBIDDEN(403, 1008, 0x0303) {
@Override
public GuacamoleException toException(String message) {
return new GuacamoleSecurityException(message);
}
},
/**
* The client took too long to respond.
*/
CLIENT_TIMEOUT(408, 1002, 0x0308) {
@Override
public GuacamoleException toException(String message) {
return new GuacamoleClientTimeoutException(message);
}
},
/**
* The client sent too much data.
*/
CLIENT_OVERRUN(413, 1009, 0x030D) {
@Override
public GuacamoleException toException(String message) {
return new GuacamoleClientOverrunException(message);
}
},
/**
* The client sent data of an unsupported or unexpected type.
*/
CLIENT_BAD_TYPE(415, 1003, 0x030F) {
@Override
public GuacamoleException toException(String message) {
return new GuacamoleClientBadTypeException(message);
}
},
/**
* The operation failed because the current client is already using too
* many resources.
*/
CLIENT_TOO_MANY(429, 1008, 0x031D) {
@Override
public GuacamoleException toException(String message) {
return new GuacamoleClientTooManyException(message);
}
};
/**
* The most applicable HTTP error code.
*/
private final int http_code;
/**
* The most applicable WebSocket error code.
*/
private final int websocket_code;
/**
* The Guacamole protocol status code.
*/
private final int guac_code;
/**
* Initializes a GuacamoleStatusCode with the given HTTP and Guacamole
* status/error code values.
*
* @param http_code The most applicable HTTP error code.
* @param websocket_code The most applicable WebSocket error code.
* @param guac_code The Guacamole protocol status code.
*/
private GuacamoleStatus(int http_code, int websocket_code, int guac_code) {
this.http_code = http_code;
this.websocket_code = websocket_code;
this.guac_code = guac_code;
}
/**
* Returns the most applicable HTTP error code.
*
* @return The most applicable HTTP error code.
*/
public int getHttpStatusCode() {
return http_code;
}
/**
* Returns the most applicable HTTP error code.
*
* @return The most applicable HTTP error code.
*/
public int getWebSocketCode() {
return websocket_code;
}
/**
* Returns the corresponding Guacamole protocol status code.
*
* @return The corresponding Guacamole protocol status code.
*/
public int getGuacamoleStatusCode() {
return guac_code;
}
/**
* Returns the GuacamoleStatus corresponding to the given Guacamole
* protocol status code. If no such GuacamoleStatus is defined, null is
* returned.
*
* @param code
* The Guacamole protocol status code to translate into a
* GuacamoleStatus.
*
* @return
* The GuacamoleStatus corresponding to the given Guacamole protocol
* status code, or null if no such GuacamoleStatus is defined.
*/
public static GuacamoleStatus fromGuacamoleStatusCode(int code) {
// Search for a GuacamoleStatus having the given status code
for (GuacamoleStatus status : values()) {
if (status.getGuacamoleStatusCode() == code)
return status;
}
// No such status found
return null;
}
/**
* Returns an instance of the {@link GuacamoleException} subclass
* corresponding to this Guacamole protocol status code. All status codes
* have a corresponding GuacamoleException except for {@link SUCCESS}. The
* returned GuacamoleException will have the provided human-readable
* message.
*
* @param message
* A human readable description of the error that occurred.
*
* @return
* An instance of the {@link GuacamoleException} subclass that
* corresponds to this status code and has the provided human-readable
* message.
*
* @throws IllegalStateException
* If invoked on {@link SUCCESS}, which has no corresponding
* GuacamoleException.
*/
public abstract GuacamoleException toException(String message);
}

View File

@@ -0,0 +1,24 @@
/*
* 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.
*/
/**
* Classes relating directly to the Guacamole protocol.
*/
package org.apache.guacamole.protocol;

View File

@@ -0,0 +1,73 @@
/*
* 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.servlet;
import org.apache.guacamole.net.DelegatingGuacamoleTunnel;
import org.apache.guacamole.net.GuacamoleTunnel;
/**
* Tracks the last time a particular GuacamoleTunnel was accessed. This
* information is not necessary for tunnels associated with WebSocket
* connections, as each WebSocket connection has its own read thread which
* continuously checks the state of the tunnel and which will automatically
* timeout when the underlying socket times out, but the HTTP tunnel has no
* such thread. Because the HTTP tunnel requires the stream to be split across
* multiple requests, tracking of activity on the tunnel must be performed
* independently of the HTTP requests.
*/
class GuacamoleHTTPTunnel extends DelegatingGuacamoleTunnel {
/**
* The last time this tunnel was accessed.
*/
private long lastAccessedTime;
/**
* Creates a new GuacamoleHTTPTunnel which wraps the given tunnel.
* Absolutely all function calls on this new GuacamoleHTTPTunnel will be
* delegated to the underlying GuacamoleTunnel.
*
* @param wrappedTunnel
* The GuacamoleTunnel to wrap within this GuacamoleHTTPTunnel.
*/
public GuacamoleHTTPTunnel(GuacamoleTunnel wrappedTunnel) {
super(wrappedTunnel);
}
/**
* Updates this tunnel, marking it as recently accessed.
*/
public void access() {
lastAccessedTime = System.currentTimeMillis();
}
/**
* Returns the time this tunnel was last accessed, as the number of
* milliseconds since midnight January 1, 1970 GMT. Tunnel access must
* be explicitly marked through calls to the access() function.
*
* @return
* The time this tunnel was last accessed.
*/
public long getLastAccessedTime() {
return lastAccessedTime;
}
}

View File

@@ -0,0 +1,211 @@
/*
* 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.servlet;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.net.GuacamoleTunnel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Map-style object which tracks in-use HTTP tunnels, automatically removing
* and closing tunnels which have not been used recently. This class is
* intended for use only within the GuacamoleHTTPTunnelServlet implementation,
* and has no real utility outside that implementation.
*/
class GuacamoleHTTPTunnelMap {
/**
* Logger for this class.
*/
private static final Logger logger = LoggerFactory.getLogger(GuacamoleHTTPTunnelMap.class);
/**
* The number of seconds to wait between tunnel accesses before timing out
* Note that this will be enforced only within a factor of 2. If a tunnel
* is unused, it will take between TUNNEL_TIMEOUT and TUNNEL_TIMEOUT*2
* seconds before that tunnel is closed and removed.
*/
private static final int TUNNEL_TIMEOUT = 15;
/**
* Executor service which runs the periodic tunnel timeout task.
*/
private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
/**
* Map of all tunnels that are using HTTP, indexed by their tunnel-specific
* session tokens.
*/
private final ConcurrentMap<String, GuacamoleHTTPTunnel> tunnelMap =
new ConcurrentHashMap<String, GuacamoleHTTPTunnel>();
/**
* Creates a new GuacamoleHTTPTunnelMap which automatically closes and
* removes HTTP tunnels which are no longer in use.
*/
public GuacamoleHTTPTunnelMap() {
// Check for unused tunnels every few seconds
executor.scheduleAtFixedRate(
new TunnelTimeoutTask(TUNNEL_TIMEOUT * 1000l),
TUNNEL_TIMEOUT, TUNNEL_TIMEOUT, TimeUnit.SECONDS);
}
/**
* Task which iterates through all registered tunnels, removing and those
* tunnels which have not been accessed for a given number of milliseconds.
*/
private class TunnelTimeoutTask implements Runnable {
/**
* The maximum amount of time to allow between accesses to any one
* HTTP tunnel, in milliseconds.
*/
private final long tunnelTimeout;
/**
* Creates a new task which automatically closes and removes tunnels
* which have not been accessed for at least the given number of
* milliseconds.
*
* @param tunnelTimeout
* The maximum amount of time to allow between separate tunnel
* read/write requests, in milliseconds.
*/
public TunnelTimeoutTask(long tunnelTimeout) {
this.tunnelTimeout = tunnelTimeout;
}
@Override
public void run() {
// Get current time
long now = System.currentTimeMillis();
// For each tunnel, close and remove any tunnels which have expired
Iterator<Map.Entry<String, GuacamoleHTTPTunnel>> entries = tunnelMap.entrySet().iterator();
while (entries.hasNext()) {
Map.Entry<String, GuacamoleHTTPTunnel> entry = entries.next();
GuacamoleHTTPTunnel tunnel = entry.getValue();
// Get elapsed time since last access
long age = now - tunnel.getLastAccessedTime();
// If tunnel is too old, close and remove it
if (age >= tunnelTimeout) {
// Remove old entry
logger.debug("HTTP tunnel \"{}\" has timed out.", entry.getKey());
entries.remove();
// Attempt to close tunnel
try {
tunnel.close();
}
catch (GuacamoleException e) {
logger.debug("Unable to close expired HTTP tunnel.", e);
}
}
} // end for each tunnel
} // end timeout task run()
}
/**
* Returns the GuacamoleTunnel associated with the given tunnel-specific
* session token, wrapped within a GuacamoleHTTPTunnel. If the no tunnel
* is associated with the given token, null is returned.
*
* @param tunnelSessionToken
* The tunnel-specific session token of the HTTP tunnel to retrieve.
*
* @return
* The GuacamoleTunnel associated with the given tunnel-specific
* session token, wrapped within a GuacamoleHTTPTunnel, if such a
* tunnel exists, or null if there is no such tunnel.
*/
public GuacamoleHTTPTunnel get(String tunnelSessionToken) {
// Update the last access time
GuacamoleHTTPTunnel tunnel = tunnelMap.get(tunnelSessionToken);
if (tunnel != null)
tunnel.access();
// Return tunnel, if any
return tunnel;
}
/**
* Registers that a new connection has been established using HTTP via the
* given GuacamoleTunnel.
*
* @param tunnelSessionToken
* The tunnel-specific session token of the HTTP tunnel being added
* (registered).
*
* @param tunnel
* The GuacamoleTunnel being registered, its associated connection
* having just been established via HTTP.
*/
public void put(String tunnelSessionToken, GuacamoleTunnel tunnel) {
tunnelMap.put(tunnelSessionToken, new GuacamoleHTTPTunnel(tunnel));
}
/**
* Removes the GuacamoleTunnel associated with the given tunnel-specific
* session token, if such a tunnel exists. The original tunnel is returned
* wrapped within a GuacamoleHTTPTunnel.
*
* @param tunnelSessionToken
* The tunnel-specific session token of the HTTP tunnel to remove
* (deregister).
*
* @return
* The GuacamoleTunnel having the given tunnel-specific session token,
* if such a tunnel exists, wrapped within a GuacamoleHTTPTunnel, or
* null if no such tunnel exists and no removal was performed.
*/
public GuacamoleHTTPTunnel remove(String tunnelSessionToken) {
return tunnelMap.remove(tunnelSessionToken);
}
/**
* Shuts down this tunnel map, disallowing future tunnels from being
* registered and reclaiming any resources.
*/
public void shutdown() {
executor.shutdownNow();
}
}

View File

@@ -0,0 +1,594 @@
/*
* 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.servlet;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.security.SecureRandom;
import java.util.Base64;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.guacamole.GuacamoleClientException;
import org.apache.guacamole.GuacamoleConnectionClosedException;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleResourceNotFoundException;
import org.apache.guacamole.GuacamoleServerException;
import org.apache.guacamole.io.GuacamoleReader;
import org.apache.guacamole.io.GuacamoleWriter;
import org.apache.guacamole.net.GuacamoleTunnel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A HttpServlet implementing and abstracting the operations required by the
* HTTP implementation of the JavaScript Guacamole client's tunnel.
*/
public abstract class GuacamoleHTTPTunnelServlet extends HttpServlet {
/**
* Logger for this class.
*/
private final Logger logger = LoggerFactory.getLogger(GuacamoleHTTPTunnelServlet.class);
/**
* Map of absolutely all active tunnels using HTTP, indexed by tunnel
* session token.
*/
private final GuacamoleHTTPTunnelMap tunnels = new GuacamoleHTTPTunnelMap();
/**
* The name of the HTTP header that contains the tunnel-specific session
* token identifying each active and distinct HTTP tunnel connection.
*/
private static final String TUNNEL_TOKEN_HEADER_NAME = "Guacamole-Tunnel-Token";
/**
* The prefix of the query string which denotes a tunnel read operation.
*/
private static final String READ_PREFIX = "read:";
/**
* The prefix of the query string which denotes a tunnel write operation.
*/
private static final String WRITE_PREFIX = "write:";
/**
* Instance of SecureRandom for generating the session token specific to
* each distinct HTTP tunnel connection.
*/
private final SecureRandom secureRandom = new SecureRandom();
/**
* Instance of Base64.Encoder for encoding random session tokens as
* strings.
*/
private final Base64.Encoder encoder = Base64.getEncoder();
/**
* Generates a new, securely-random session token that may be used to
* represent the ongoing communication session of a distinct HTTP tunnel
* connection.
*
* @return
* A new, securely-random session token.
*/
protected String generateToken() {
byte[] bytes = new byte[33];
secureRandom.nextBytes(bytes);
return encoder.encodeToString(bytes);
}
/**
* Registers the given tunnel such that future read/write requests to that
* tunnel will be properly directed.
*
* @deprecated
* This function has been deprecated in favor of {@link #registerTunnel(java.lang.String, org.apache.guacamole.net.GuacamoleTunnel)},
* which decouples identification of HTTP tunnel sessions from the
* tunnel UUID.
*
* @param tunnel
* The tunnel to register.
*/
@Deprecated
protected void registerTunnel(GuacamoleTunnel tunnel) {
registerTunnel(tunnel.getUUID().toString(), tunnel);
}
/**
* Registers the given HTTP tunnel such that future read/write requests
* including the given tunnel-specific session token will be properly
* directed. The session token must be unpredictable (securely-random) and
* unique across all active HTTP tunnels. It is recommended that each HTTP
* tunnel session token be obtained through calling {@link #generateToken()}.
*
* @param tunnelSessionToken
* The tunnel-specific session token to associate with the HTTP tunnel
* being registered.
*
* @param tunnel
* The tunnel to register.
*/
protected void registerTunnel(String tunnelSessionToken, GuacamoleTunnel tunnel) {
tunnels.put(tunnelSessionToken, tunnel);
logger.debug("Registered tunnel \"{}\".", tunnel.getUUID());
}
/**
* Deregisters the given tunnel such that future read/write requests to
* that tunnel will be rejected.
*
* @deprecated
* This function has been deprecated in favor of {@link #deregisterTunnel(java.lang.String)},
* which decouples identification of HTTP tunnel sessions from the
* tunnel UUID.
*
* @param tunnel
* The tunnel to deregister.
*/
@Deprecated
protected void deregisterTunnel(GuacamoleTunnel tunnel) {
deregisterTunnel(tunnel.getUUID().toString());
}
/**
* Deregisters the HTTP tunnel associated with the given tunnel-specific
* session token such that future read/write requests to that tunnel will
* be rejected. Each HTTP tunnel must be associated with a session token
* unique to that tunnel via a call {@link #registerTunnel(java.lang.String, org.apache.guacamole.net.GuacamoleTunnel)}.
*
* @param tunnelSessionToken
* The tunnel-specific session token associated with the HTTP tunnel
* being deregistered.
*/
protected void deregisterTunnel(String tunnelSessionToken) {
GuacamoleTunnel tunnel = tunnels.remove(tunnelSessionToken);
if (tunnel != null)
logger.debug("Deregistered tunnel \"{}\".", tunnel.getUUID());
}
/**
* Returns the tunnel associated with the given tunnel-specific session
* token, if it has been registered with {@link #registerTunnel(java.lang.String, org.apache.guacamole.net.GuacamoleTunnel)}
* and not yet deregistered with {@link #deregisterTunnel(java.lang.String)}.
*
* @param tunnelSessionToken
* The tunnel-specific session token associated with the HTTP tunnel to
* be retrieved.
*
* @return
* The tunnel corresponding to the given session token.
*
* @throws GuacamoleException
* If the requested tunnel does not exist because it has not yet been
* registered or it has been deregistered.
*/
protected GuacamoleTunnel getTunnel(String tunnelSessionToken)
throws GuacamoleException {
// Pull tunnel from map
GuacamoleTunnel tunnel = tunnels.get(tunnelSessionToken);
if (tunnel == null)
throw new GuacamoleResourceNotFoundException("No such tunnel.");
return tunnel;
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException {
handleTunnelRequest(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException {
handleTunnelRequest(request, response);
}
/**
* Sends an error on the given HTTP response using the information within
* the given GuacamoleStatus.
*
* @param response
* The HTTP response to use to send the error.
*
* @param guacamoleStatusCode
* The GuacamoleStatus code to send.
*
* @param guacamoleHttpCode
* The numeric HTTP code to send.
*
* @param message
* The human-readable error message to send.
*
* @throws ServletException
* If an error prevents sending of the error code.
*/
protected void sendError(HttpServletResponse response, int guacamoleStatusCode,
int guacamoleHttpCode, String message)
throws ServletException {
try {
// If response not committed, send error code and message
if (!response.isCommitted()) {
response.addHeader("Guacamole-Status-Code", Integer.toString(guacamoleStatusCode));
response.addHeader("Guacamole-Error-Message", message);
response.sendError(guacamoleHttpCode);
}
}
catch (IOException ioe) {
// If unable to send error at all due to I/O problems,
// rethrow as servlet exception
throw new ServletException(ioe);
}
}
/**
* Dispatches every HTTP GET and POST request to the appropriate handler
* function based on the query string.
*
* @param request
* The HttpServletRequest associated with the GET or POST request
* received.
*
* @param response
* The HttpServletResponse associated with the GET or POST request
* received.
*
* @throws ServletException
* If an error occurs while servicing the request.
*/
protected void handleTunnelRequest(HttpServletRequest request,
HttpServletResponse response) throws ServletException {
try {
String query = request.getQueryString();
if (query == null)
throw new GuacamoleClientException("No query string provided.");
// If connect operation, call doConnect() and return tunnel
// session token and UUID in response
if (query.equals("connect")) {
GuacamoleTunnel tunnel = doConnect(request);
if (tunnel == null)
throw new GuacamoleResourceNotFoundException("No tunnel created.");
// Register newly-created tunnel
String tunnelSessionToken = generateToken();
registerTunnel(tunnelSessionToken, tunnel);
try {
// Ensure buggy browsers do not cache response
response.setHeader("Cache-Control", "no-cache");
// Include tunnel session token for future requests
response.setHeader(TUNNEL_TOKEN_HEADER_NAME, tunnelSessionToken);
// Send UUID to client
response.getWriter().print(tunnel.getUUID().toString());
}
catch (IOException e) {
throw new GuacamoleServerException(e);
}
// Connection successful
return;
}
// Pull tunnel-specific session token from request
String tunnelSessionToken = request.getHeader(TUNNEL_TOKEN_HEADER_NAME);
if (tunnelSessionToken == null)
throw new GuacamoleClientException("The HTTP tunnel session "
+ "token is required for all requests after "
+ "connecting.");
// Dispatch valid tunnel read/write operations
if (query.startsWith(READ_PREFIX))
doRead(request, response, tunnelSessionToken);
else if (query.startsWith(WRITE_PREFIX))
doWrite(request, response, tunnelSessionToken);
// Otherwise, invalid operation
else
throw new GuacamoleClientException("Invalid tunnel operation: " + query);
}
// Catch any thrown guacamole exception and attempt to pass within the
// HTTP response, logging each error appropriately.
catch (GuacamoleClientException e) {
logger.warn("HTTP tunnel request rejected: {}", e.getMessage());
sendError(response, e.getStatus().getGuacamoleStatusCode(),
e.getStatus().getHttpStatusCode(), e.getMessage());
}
catch (GuacamoleException e) {
logger.error("HTTP tunnel request failed: {}", e.getMessage());
logger.debug("Internal error in HTTP tunnel.", e);
sendError(response, e.getStatus().getGuacamoleStatusCode(),
e.getStatus().getHttpStatusCode(), "Internal server error.");
}
}
/**
* Called whenever the JavaScript Guacamole client makes a connection
* request via HTTP. It it up to the implementor of this function to define
* what conditions must be met for a tunnel to be configured and returned
* as a result of this connection request (whether some sort of credentials
* must be specified, for example).
*
* @param request
* The HttpServletRequest associated with the connection request
* received. Any parameters specified along with the connection request
* can be read from this object.
*
* @return
* A newly constructed GuacamoleTunnel if successful, null otherwise.
*
* @throws GuacamoleException
* If an error occurs while constructing the GuacamoleTunnel, or if the
* conditions required for connection are not met.
*/
protected abstract GuacamoleTunnel doConnect(HttpServletRequest request)
throws GuacamoleException;
/**
* Called whenever the JavaScript Guacamole client makes a read request.
* This function should in general not be overridden, as it already
* contains a proper implementation of the read operation.
*
* @param request
* The HttpServletRequest associated with the read request received.
*
* @param response
* The HttpServletResponse associated with the write request received.
* Any data to be sent to the client in response to the write request
* should be written to the response body of this HttpServletResponse.
*
* @param tunnelSessionToken
* The tunnel-specific session token of the HTTP tunnel to read from,
* as specified in the read request. This tunnel must have been created
* by a previous call to doConnect().
*
* @throws GuacamoleException
* If an error occurs while handling the read request.
*/
protected void doRead(HttpServletRequest request,
HttpServletResponse response, String tunnelSessionToken)
throws GuacamoleException {
// Get tunnel, ensure tunnel exists
GuacamoleTunnel tunnel = getTunnel(tunnelSessionToken);
// Ensure tunnel is open
if (!tunnel.isOpen())
throw new GuacamoleResourceNotFoundException("Tunnel is closed.");
// Obtain exclusive read access
GuacamoleReader reader = tunnel.acquireReader();
try {
// Note that although we are sending text, Webkit browsers will
// buffer 1024 bytes before starting a normal stream if we use
// anything but application/octet-stream.
response.setContentType("application/octet-stream");
response.setHeader("Cache-Control", "no-cache");
// Get writer for response
Writer out = new BufferedWriter(new OutputStreamWriter(
response.getOutputStream(), "UTF-8"));
// Stream data to response, ensuring output stream is closed
try {
// Deregister tunnel and throw error if we reach EOF without
// having ever sent any data
char[] message = reader.read();
if (message == null)
throw new GuacamoleConnectionClosedException("Tunnel reached end of stream.");
// For all messages, until another stream is ready (we send at least one message)
do {
// Get message output bytes
out.write(message, 0, message.length);
// Flush if we expect to wait
if (!reader.available()) {
out.flush();
response.flushBuffer();
}
// No more messages another stream can take over
if (tunnel.hasQueuedReaderThreads())
break;
} while (tunnel.isOpen() && (message = reader.read()) != null);
// Close tunnel immediately upon EOF
if (message == null) {
deregisterTunnel(tunnelSessionToken);
tunnel.close();
}
// End-of-instructions marker
out.write("0.;");
out.flush();
response.flushBuffer();
}
// Send end-of-stream marker and close tunnel if connection is closed
catch (GuacamoleConnectionClosedException e) {
// Deregister and close
deregisterTunnel(tunnelSessionToken);
tunnel.close();
// End-of-instructions marker
out.write("0.;");
out.flush();
response.flushBuffer();
}
catch (GuacamoleException e) {
// Deregister and close
deregisterTunnel(tunnelSessionToken);
tunnel.close();
throw e;
}
// Always close output stream
finally {
out.close();
}
}
catch (IOException e) {
// Log typically frequent I/O error if desired
logger.debug("Error writing to servlet output stream", e);
// Deregister and close
deregisterTunnel(tunnelSessionToken);
tunnel.close();
}
finally {
tunnel.releaseReader();
}
}
/**
* Called whenever the JavaScript Guacamole client makes a write request.
* This function should in general not be overridden, as it already
* contains a proper implementation of the write operation.
*
* @param request
* The HttpServletRequest associated with the write request received.
* Any data to be written will be specified within the body of this
* request.
*
* @param response
* The HttpServletResponse associated with the write request received.
*
* @param tunnelSessionToken
* The tunnel-specific session token of the HTTP tunnel to write to,
* as specified in the write request. This tunnel must have been created
* by a previous call to doConnect().
*
* @throws GuacamoleException
* If an error occurs while handling the write request.
*/
protected void doWrite(HttpServletRequest request,
HttpServletResponse response, String tunnelSessionToken)
throws GuacamoleException {
GuacamoleTunnel tunnel = getTunnel(tunnelSessionToken);
// We still need to set the content type to avoid the default of
// text/html, as such a content type would cause some browsers to
// attempt to parse the result, even though the JavaScript client
// does not explicitly request such parsing.
response.setContentType("application/octet-stream");
response.setHeader("Cache-Control", "no-cache");
response.setContentLength(0);
// Send data
try {
// Get writer from tunnel
GuacamoleWriter writer = tunnel.acquireWriter();
// Get input reader for HTTP stream
Reader input = new InputStreamReader(
request.getInputStream(), "UTF-8");
// Transfer data from input stream to tunnel output, ensuring
// input is always closed
try {
// Buffer
int length;
char[] buffer = new char[8192];
// Transfer data using buffer
while (tunnel.isOpen() &&
(length = input.read(buffer, 0, buffer.length)) != -1)
writer.write(buffer, 0, length);
}
// Close input stream in all cases
finally {
input.close();
}
}
catch (GuacamoleConnectionClosedException e) {
logger.debug("Connection to guacd closed.", e);
}
catch (IOException e) {
// Deregister and close
deregisterTunnel(tunnelSessionToken);
tunnel.close();
throw new GuacamoleServerException("I/O Error sending data to server: " + e.getMessage(), e);
}
finally {
tunnel.releaseWriter();
}
}
@Override
public void destroy() {
tunnels.shutdown();
}
}
/**
* \example ExampleTunnelServlet.java
*
* A basic example demonstrating extending GuacamoleTunnelServlet and
* implementing doConnect() to configure the Guacamole connection as
* desired.
*/

View File

@@ -0,0 +1,25 @@
/*
* 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.
*/
/**
* Classes which build upon the Java Servlet API, providing an HTTP-based
* tunnel and session management.
*/
package org.apache.guacamole.servlet;

View File

@@ -0,0 +1,373 @@
/*
* 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.websocket;
import java.io.IOException;
import java.util.List;
import javax.websocket.CloseReason;
import javax.websocket.CloseReason.CloseCode;
import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.MessageHandler;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.RemoteEndpoint;
import javax.websocket.Session;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.io.GuacamoleReader;
import org.apache.guacamole.io.GuacamoleWriter;
import org.apache.guacamole.net.GuacamoleTunnel;
import org.apache.guacamole.GuacamoleClientException;
import org.apache.guacamole.GuacamoleConnectionClosedException;
import org.apache.guacamole.protocol.FilteredGuacamoleWriter;
import org.apache.guacamole.protocol.GuacamoleFilter;
import org.apache.guacamole.protocol.GuacamoleInstruction;
import org.apache.guacamole.protocol.GuacamoleStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A WebSocket implementation of GuacamoleTunnel functionality, compatible with
* the Guacamole.WebSocketTunnel object included with the JavaScript API.
* Messages sent/received are simply chunks of the Guacamole protocol
* instruction stream.
*/
public abstract class GuacamoleWebSocketTunnelEndpoint extends Endpoint {
/**
* The default, minimum buffer size for instructions.
*/
private static final int BUFFER_SIZE = 8192;
/**
* The opcode of the instruction used to indicate a connection stability
* test ping request or response. Note that this instruction is
* encapsulated within an internal tunnel instruction (with the opcode
* being the empty string), thus this will actually be the value of the
* first element of the received instruction.
*/
private static final String PING_OPCODE = "ping";
/**
* Logger for this class.
*/
private final Logger logger = LoggerFactory.getLogger(GuacamoleWebSocketTunnelEndpoint.class);
/**
* The underlying GuacamoleTunnel. WebSocket reads/writes will be handled
* as reads/writes to this tunnel. This value may be null if no connection
* has been established.
*/
private GuacamoleTunnel tunnel;
/**
* Remote (client) side of this connection. This value will always be
* non-null if tunnel is non-null.
*/
private RemoteEndpoint.Basic remote;
/**
* Sends the numeric Guacaomle Status Code and Web Socket
* code and closes the connection.
*
* @param session
* The outbound WebSocket connection to close.
*
* @param guacamoleStatusCode
* The numeric Guacamole status to send.
*
* @param webSocketCode
* The numeric WebSocket status to send.
*/
private void closeConnection(Session session, int guacamoleStatusCode,
int webSocketCode) {
try {
CloseCode code = CloseReason.CloseCodes.getCloseCode(webSocketCode);
String message = Integer.toString(guacamoleStatusCode);
session.close(new CloseReason(code, message));
}
catch (IOException e) {
logger.debug("Unable to close WebSocket connection.", e);
}
}
/**
* Sends the given Guacaomle Status and closes the given
* connection.
*
* @param session
* The outbound WebSocket connection to close.
*
* @param guacStatus
* The status to use for the connection.
*/
private void closeConnection(Session session, GuacamoleStatus guacStatus) {
closeConnection(session, guacStatus.getGuacamoleStatusCode(),
guacStatus.getWebSocketCode());
}
/**
* Sends a Guacamole instruction along the outbound WebSocket connection to
* the connected Guacamole client. If an instruction is already in the
* process of being sent by another thread, this function will block until
* in-progress instructions are complete.
*
* @param instruction
* The instruction to send.
*
* @throws IOException
* If an I/O error occurs preventing the given instruction from being
* sent.
*/
private void sendInstruction(String instruction)
throws IOException {
// NOTE: Synchronization on the non-final remote field here is
// intentional. The remote (the outbound websocket connection) is only
// sensitive to simultaneous attempts to send messages with respect to
// itself. If the remote changes, then the outbound websocket
// connection has changed, and synchronization need only be performed
// in context of the new remote.
synchronized (remote) {
remote.sendText(instruction);
}
}
/**
* Sends a Guacamole instruction along the outbound WebSocket connection to
* the connected Guacamole client. If an instruction is already in the
* process of being sent by another thread, this function will block until
* in-progress instructions are complete.
*
* @param instruction
* The instruction to send.
*
* @throws IOException
* If an I/O error occurs preventing the given instruction from being
* sent.
*/
private void sendInstruction(GuacamoleInstruction instruction)
throws IOException {
sendInstruction(instruction.toString());
}
/**
* Returns a new tunnel for the given session. How this tunnel is created
* or retrieved is implementation-dependent.
*
* @param session The session associated with the active WebSocket
* connection.
* @param config Configuration information associated with the instance of
* the endpoint created for handling this single connection.
* @return A connected tunnel, or null if no such tunnel exists.
* @throws GuacamoleException If an error occurs while retrieving the
* tunnel, or if access to the tunnel is denied.
*/
protected abstract GuacamoleTunnel createTunnel(Session session, EndpointConfig config)
throws GuacamoleException;
@Override
@OnOpen
public void onOpen(final Session session, EndpointConfig config) {
// Store underlying remote for future use via sendInstruction()
remote = session.getBasicRemote();
try {
// Get tunnel
tunnel = createTunnel(session, config);
if (tunnel == null) {
closeConnection(session, GuacamoleStatus.RESOURCE_NOT_FOUND);
return;
}
}
catch (GuacamoleException e) {
logger.error("Creation of WebSocket tunnel to guacd failed: {}", e.getMessage());
logger.debug("Error connecting WebSocket tunnel.", e);
closeConnection(session, e.getStatus().getGuacamoleStatusCode(),
e.getWebSocketCode());
return;
}
// Manually register message handler
session.addMessageHandler(new MessageHandler.Whole<String>() {
@Override
public void onMessage(String message) {
GuacamoleWebSocketTunnelEndpoint.this.onMessage(message);
}
});
// Prepare read transfer thread
Thread readThread = new Thread() {
@Override
public void run() {
StringBuilder buffer = new StringBuilder(BUFFER_SIZE);
GuacamoleReader reader = tunnel.acquireReader();
char[] readMessage;
try {
// Send tunnel UUID
sendInstruction(new GuacamoleInstruction(
GuacamoleTunnel.INTERNAL_DATA_OPCODE,
tunnel.getUUID().toString()
));
try {
// Attempt to read
while ((readMessage = reader.read()) != null) {
// Buffer message
buffer.append(readMessage);
// Flush if we expect to wait or buffer is getting full
if (!reader.available() || buffer.length() >= BUFFER_SIZE) {
sendInstruction(buffer.toString());
buffer.setLength(0);
}
}
// No more data
closeConnection(session, GuacamoleStatus.SUCCESS);
}
// Catch any thrown guacamole exception and attempt
// to pass within the WebSocket connection, logging
// each error appropriately.
catch (GuacamoleClientException e) {
logger.info("WebSocket connection terminated: {}", e.getMessage());
logger.debug("WebSocket connection terminated due to client error.", e);
closeConnection(session, e.getStatus().getGuacamoleStatusCode(),
e.getWebSocketCode());
}
catch (GuacamoleConnectionClosedException e) {
logger.debug("Connection to guacd closed.", e);
closeConnection(session, GuacamoleStatus.SUCCESS);
}
catch (GuacamoleException e) {
logger.error("Connection to guacd terminated abnormally: {}", e.getMessage());
logger.debug("Internal error during connection to guacd.", e);
closeConnection(session, e.getStatus().getGuacamoleStatusCode(),
e.getWebSocketCode());
}
}
catch (IOException e) {
logger.debug("I/O error prevents further reads.", e);
closeConnection(session, GuacamoleStatus.SERVER_ERROR);
}
}
};
readThread.start();
}
@OnMessage
public void onMessage(String message) {
// Ignore inbound messages if there is no associated tunnel
if (tunnel == null)
return;
// Filter received instructions, handling tunnel-internal instructions
// without passing through to guacd
GuacamoleWriter writer = new FilteredGuacamoleWriter(tunnel.acquireWriter(), new GuacamoleFilter() {
@Override
public GuacamoleInstruction filter(GuacamoleInstruction instruction)
throws GuacamoleException {
// Filter out all tunnel-internal instructions
if (instruction.getOpcode().equals(GuacamoleTunnel.INTERNAL_DATA_OPCODE)) {
// Respond to ping requests
List<String> args = instruction.getArgs();
if (args.size() >= 2 && args.get(0).equals(PING_OPCODE)) {
try {
sendInstruction(new GuacamoleInstruction(
GuacamoleTunnel.INTERNAL_DATA_OPCODE,
PING_OPCODE, args.get(1)
));
}
catch (IOException e) {
logger.debug("Unable to send \"ping\" response for WebSocket tunnel.", e);
}
}
return null;
}
// Pass through all non-internal instructions untouched
return instruction;
}
});
try {
// Write received message
writer.write(message.toCharArray());
}
catch (GuacamoleConnectionClosedException e) {
logger.debug("Connection to guacd closed.", e);
}
catch (GuacamoleException e) {
logger.debug("WebSocket tunnel write failed.", e);
}
tunnel.releaseWriter();
}
@Override
@OnClose
public void onClose(Session session, CloseReason closeReason) {
try {
if (tunnel != null)
tunnel.close();
}
catch (GuacamoleException e) {
logger.debug("Unable to close WebSocket tunnel.", e);
}
}
}

View File

@@ -0,0 +1,120 @@
/*
* 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.io;
import java.io.StringReader;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.protocol.GuacamoleInstruction;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Tests the ReaderGuacamoleReader implementation of GuacamoleReader, validating
* that instructions are parsed correctly.
*/
public class ReaderGuacamoleReaderTest {
/**
* Test of ReaderGuacamoleReader parsing.
*
* @throws GuacamoleException If a parse error occurs while parsing the
* known-good test string.
*/
@Test
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;1.\uD83E\uDD79;";
GuacamoleReader reader = new ReaderGuacamoleReader(new StringReader(test));
GuacamoleInstruction instruction;
// Validate first test instruction
instruction = reader.readInstruction();
assertNotNull(instruction);
assertEquals(3, instruction.getArgs().size());
assertEquals("a", instruction.getOpcode());
assertEquals("bc", instruction.getArgs().get(0));
assertEquals("def", instruction.getArgs().get(1));
assertEquals("helloworld", instruction.getArgs().get(2));
// Validate second test instruction
instruction = reader.readInstruction();
assertNotNull(instruction);
assertEquals(1, instruction.getArgs().size());
assertEquals("test", instruction.getOpcode());
assertEquals("test2", instruction.getArgs().get(0));
// Validate third test instruction
instruction = reader.readInstruction();
assertNotNull(instruction);
assertEquals(0, instruction.getArgs().size());
assertEquals("", instruction.getOpcode());
// Validate fourth test instruction
instruction = reader.readInstruction();
assertNotNull(instruction);
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);
}
/**
* Test of ReaderGuacamoleReader's read method.
*
* @throws GuacamoleException If an error occurs while reading the instructions.
*/
@Test
public void testRead() throws GuacamoleException {
// Test string containing multiple instructions
final String test = "3.foo,3.bar;2.az,4.bazz;";
ReaderGuacamoleReader reader = new ReaderGuacamoleReader(new StringReader(test));
// Expected character arrays for the instructions
char[] expectedFirstInstruction = "3.foo,3.bar;".toCharArray();
char[] expectedSecondInstruction = "2.az,4.bazz;".toCharArray();
// Read first instruction and verify
char[] firstInstructionChars = reader.read();
assertNotNull(firstInstructionChars);
assertArrayEquals(expectedFirstInstruction, firstInstructionChars);
// Read second instruction and verify
char[] secondInstructionChars = reader.read();
assertNotNull(secondInstructionChars);
assertArrayEquals(expectedSecondInstruction, secondInstructionChars);
// Verify that there are no more instructions
assertNull(reader.read());
}
}

View File

@@ -0,0 +1,90 @@
/*
* 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.io.StringReader;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.io.GuacamoleReader;
import org.apache.guacamole.io.ReaderGuacamoleReader;
import static org.junit.Assert.*;
import org.junit.Test;
/**
* Test which validates filtering of Guacamole instructions with
* FilteredGuacamoleReader.
*/
public class FilteredGuacamoleReaderTest {
/**
* Filter which allows through "yes" instructions but drops all others.
*/
private static class TestFilter implements GuacamoleFilter {
@Override
public GuacamoleInstruction filter(GuacamoleInstruction instruction) throws GuacamoleException {
if (instruction.getOpcode().equals("yes"))
return instruction;
return null;
}
}
@Test
public void testFilter() throws Exception {
// Test string
final String test = "3.yes,1.A;2.no,1.B;3.yes,1.C;3.yes,1.D;4.nope,1.E;";
GuacamoleReader reader = new FilteredGuacamoleReader(new ReaderGuacamoleReader(new StringReader(test)),
new TestFilter());
GuacamoleInstruction instruction;
// Validate first instruction
instruction = reader.readInstruction();
assertNotNull(instruction);
assertEquals("yes", instruction.getOpcode());
assertEquals(1, instruction.getArgs().size());
assertEquals("A", instruction.getArgs().get(0));
// Validate second instruction
instruction = reader.readInstruction();
assertNotNull(instruction);
assertEquals("yes", instruction.getOpcode());
assertEquals(1, instruction.getArgs().size());
assertEquals("C", instruction.getArgs().get(0));
// Validate third instruction
instruction = reader.readInstruction();
assertNotNull(instruction);
assertEquals("yes", instruction.getOpcode());
assertEquals(1, instruction.getArgs().size());
assertEquals("D", instruction.getArgs().get(0));
// Should be done now
instruction = reader.readInstruction();
assertNull(instruction);
}
}

View File

@@ -0,0 +1,68 @@
/*
* 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.io.StringWriter;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.io.GuacamoleWriter;
import org.apache.guacamole.io.WriterGuacamoleWriter;
import static org.junit.Assert.*;
import org.junit.Test;
/**
* Test which validates filtering of Guacamole instructions with
* FilteredGuacamoleWriter.
*/
public class FilteredGuacamoleWriterTest {
/**
* Filter which allows through "yes" instructions but drops all others.
*/
private static class TestFilter implements GuacamoleFilter {
@Override
public GuacamoleInstruction filter(GuacamoleInstruction instruction) throws GuacamoleException {
if (instruction.getOpcode().equals("yes"))
return instruction;
return null;
}
}
@Test
public void testFilter() throws Exception {
StringWriter stringWriter = new StringWriter();
GuacamoleWriter writer = new FilteredGuacamoleWriter(new WriterGuacamoleWriter(stringWriter),
new TestFilter());
// Write a few chunks of complete instructions
writer.write("3.yes,1.A;2.no,1.B;3.yes,1.C;3.yes,1.D;4.nope,1.E;".toCharArray());
writer.write("1.n,3.abc;3.yes,5.hello;2.no,4.test;3.yes,5.world;".toCharArray());
// Validate filtered results
assertEquals("3.yes,1.A;3.yes,1.C;3.yes,1.D;3.yes,5.hello;3.yes,5.world;", stringWriter.toString());
}
}

View File

@@ -0,0 +1,205 @@
/*
* 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.Collections;
import java.util.List;
import static org.junit.Assert.*;
import org.junit.Test;
/**
* Unit test for GuacamoleParser. Verifies that parsing of the Guacamole
* protocol works as required.
*/
public class GuacamoleInstructionTest {
/**
* A single test case for verifying that Guacamole protocol implementations
* correctly parse or encode Guacamole instructions.
*/
public static class TestCase extends GuacamoleInstruction {
/**
* The full and correct Guacamole protocol representation of this
* instruction.
*/
public final String UNPARSED;
/**
* The opcode that should be present in the Guacamole instruction;
*/
public final String OPCODE;
/**
* All arguments that should be present in the Guacamole instruction;
*/
public final List<String> ARGS;
/**
* Creates a new TestCase representing the given Guacamole instruction.
*
* @param unparsed
* The full and correct Guacamole protocol representation of this
* instruction.
*
* @param opcode
* The opcode of the Guacamole instruction.
*
* @param args
* The arguments of the Guacamole instruction, if any.
*/
public TestCase(String unparsed, String opcode, String... args) {
super(opcode, Arrays.copyOf(args, args.length));
this.UNPARSED = unparsed;
this.OPCODE = opcode;
this.ARGS = Collections.unmodifiableList(Arrays.asList(args));
}
}
/**
* A single Unicode high surrogate character (any character between U+D800
* and U+DB7F).
*/
public static final String HIGH_SURROGATE = "\uD802";
/**
* A single Unicode low surrogate character (any character between U+DC00
* and U+DFFF).
*/
public static final String LOW_SURROGATE = "\uDF00";
/**
* A Unicode surrogate pair, consisting of a high and low surrogate.
*/
public static final String SURROGATE_PAIR = HIGH_SURROGATE + LOW_SURROGATE;
/**
* A 4-character test string containing Unicode characters that require
* multiple bytes when encoded as UTF-8, including at least one character
* that is encoded as a surrogate pair in UTF-16.
*/
public static final String UTF8_MULTIBYTE = "\u72AC" + SURROGATE_PAIR + "z\u00C1";
/**
* Pre-defined set of test cases for verifying Guacamole instructions are
* correctly parsed and encoded.
*/
public static List<TestCase> TEST_CASES = Collections.unmodifiableList(Arrays.asList(
// Empty instruction
new TestCase(
"0.;",
""
),
// Instruction using basic Latin characters
new TestCase(
"5.test2,"
+ "10.hellohello,"
+ "15.worldworldworld;",
"test2",
"hellohello",
"worldworldworld"
),
// Instruction using characters requiring multiple bytes in UTF-8 and
// surrogate pairs in UTF-16, including an element ending with a surrogate
// pair
new TestCase(
"4.ab" + HIGH_SURROGATE + HIGH_SURROGATE + ","
+ "6.a" + UTF8_MULTIBYTE + "b,"
+ "5.12345,"
+ "10.a" + UTF8_MULTIBYTE + UTF8_MULTIBYTE + "c;",
"ab" + HIGH_SURROGATE + HIGH_SURROGATE,
"a" + UTF8_MULTIBYTE + "b",
"12345",
"a" + UTF8_MULTIBYTE + UTF8_MULTIBYTE + "c"
),
// Instruction with an element values ending with an incomplete surrogate
// pair (high or low surrogate only)
new TestCase(
"4.test,"
+ "5.1234" + HIGH_SURROGATE + ","
+ "5.4567" + LOW_SURROGATE + ";",
"test",
"1234" + HIGH_SURROGATE,
"4567" + LOW_SURROGATE
),
// Instruction with element values containing incomplete surrogate pairs
new TestCase(
"5.te" + LOW_SURROGATE + "st,"
+ "5.12" + HIGH_SURROGATE + "3" + LOW_SURROGATE + ","
+ "6.5" + LOW_SURROGATE + LOW_SURROGATE + "4" + HIGH_SURROGATE + HIGH_SURROGATE + ","
+ "10." + UTF8_MULTIBYTE + HIGH_SURROGATE + UTF8_MULTIBYTE + HIGH_SURROGATE + ";",
"te" + LOW_SURROGATE + "st",
"12" + HIGH_SURROGATE + "3" + LOW_SURROGATE,
"5" + LOW_SURROGATE + LOW_SURROGATE + "4" + HIGH_SURROGATE + HIGH_SURROGATE,
UTF8_MULTIBYTE + HIGH_SURROGATE + UTF8_MULTIBYTE + HIGH_SURROGATE
)
));
/**
* Verifies that instruction opcodes are represented correctly.
*/
@Test
public void testGetOpcode() {
for (TestCase testCase : TEST_CASES) {
assertEquals(testCase.OPCODE, testCase.getOpcode());
}
}
/**
* Verifies that instruction arguments are represented correctly.
*/
@Test
public void testGetArgs() {
for (TestCase testCase : TEST_CASES) {
assertEquals(testCase.ARGS, testCase.getArgs());
}
}
/**
* Verifies that instructions are encoded correctly.
*/
@Test
public void testToString() {
for (TestCase testCase : TEST_CASES) {
assertEquals(testCase.UNPARSED, testCase.toString());
}
}
}

View File

@@ -0,0 +1,87 @@
/*
* 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 org.apache.guacamole.GuacamoleException;
import static org.apache.guacamole.protocol.GuacamoleInstructionTest.TEST_CASES;
import org.apache.guacamole.protocol.GuacamoleInstructionTest.TestCase;
import static org.junit.Assert.*;
import org.junit.Test;
/**
* Unit test for GuacamoleParser. Verifies that parsing of the Guacamole
* protocol works as required.
*/
public class GuacamoleParserTest {
/**
* The GuacamoleParser instance being tested.
*/
private final GuacamoleParser parser = new GuacamoleParser();
/**
* Verify that GuacamoleParser correctly parses each of the instruction
* test cases included in the GuacamoleInstruction test.
*
* @throws GuacamoleException
* If a parse error occurs.
*/
@Test
public void testParser() throws GuacamoleException {
// Build buffer containing all of the instruction test cases, one after
// the other
StringBuilder allTestCases = new StringBuilder();
for (TestCase testCase : TEST_CASES)
allTestCases.append(testCase.UNPARSED);
// Prepare buffer and offsets for feeding the data into the parser as
// if received over the network
char buffer[] = allTestCases.toString().toCharArray();
int offset = 0;
int length = buffer.length;
// Verify that each of the expected instructions is received in order
for (TestCase testCase : TEST_CASES) {
// Feed data into parser until parser refuses to receive more data
int parsed;
while (length > 0 && (parsed = parser.append(buffer, offset, length)) != 0) {
offset += parsed;
length -= parsed;
}
// An instruction should now be parsed and ready for retrieval
assertTrue(parser.hasNext());
// Verify instruction contains expected opcode and args
GuacamoleInstruction instruction = parser.next();
assertNotNull(instruction);
assertEquals(testCase.OPCODE, instruction.getOpcode());
assertEquals(testCase.ARGS, instruction.getArgs());
}
// There should be no more instructions
assertFalse(parser.hasNext());
}
}

View File

@@ -0,0 +1,151 @@
/*
* 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 org.junit.Assert;
import org.junit.Test;
/**
* Unit test for GuacamoleProtocolVersion. Verifies that Guacamole protocol
* version string parsing works as required.
*/
public class GuacamoleProtocolVersionTest {
/**
* Verifies that valid version strings are parsed successfully.
*/
@Test
public void testValidVersionParse() {
GuacamoleProtocolVersion version = GuacamoleProtocolVersion.parseVersion("VERSION_012_102_398");
Assert.assertNotNull(version);
Assert.assertEquals(12, version.getMajor());
Assert.assertEquals(102, version.getMinor());
Assert.assertEquals(398, version.getPatch());
}
/**
* Verifies that invalid version strings fail to parse.
*/
@Test
public void testInvalidVersionParse() {
Assert.assertNull(GuacamoleProtocolVersion.parseVersion("potato"));
Assert.assertNull(GuacamoleProtocolVersion.parseVersion("VERSION_"));
Assert.assertNull(GuacamoleProtocolVersion.parseVersion("VERSION___"));
Assert.assertNull(GuacamoleProtocolVersion.parseVersion("VERSION__2_3"));
Assert.assertNull(GuacamoleProtocolVersion.parseVersion("VERSION_1__3"));
Assert.assertNull(GuacamoleProtocolVersion.parseVersion("VERSION_1_2_"));
Assert.assertNull(GuacamoleProtocolVersion.parseVersion("VERSION_A_2_3"));
Assert.assertNull(GuacamoleProtocolVersion.parseVersion("VERSION_1_B_3"));
Assert.assertNull(GuacamoleProtocolVersion.parseVersion("VERSION_1_2_C"));
Assert.assertNull(GuacamoleProtocolVersion.parseVersion("_1_2_3"));
Assert.assertNull(GuacamoleProtocolVersion.parseVersion("version_1_2_3"));
}
/**
* Verifies that the atLeast() function defined by GuacamoleProtocolVersion
* behaves as required for a series of three versions which are in strictly
* increasing order (a &lt; b &lt; c).
*
* @param a
* The String representation of the version which is known to be the
* smaller than versions b and c.
*
* @param b
* The String representation of the version which is known to be
* larger than version a but smaller than version c.
*
* @param c
* The String representation of the version which is known to be the
* larger than versions a and b.
*/
private void testVersionCompare(String a, String b, String c) {
GuacamoleProtocolVersion verA = GuacamoleProtocolVersion.parseVersion(a);
GuacamoleProtocolVersion verB = GuacamoleProtocolVersion.parseVersion(b);
GuacamoleProtocolVersion verC = GuacamoleProtocolVersion.parseVersion(c);
Assert.assertTrue(verC.atLeast(verB));
Assert.assertTrue(verC.atLeast(verA));
Assert.assertTrue(verB.atLeast(verA));
Assert.assertFalse(verB.atLeast(verC));
Assert.assertFalse(verA.atLeast(verC));
Assert.assertFalse(verA.atLeast(verB));
Assert.assertTrue(verA.atLeast(verA));
Assert.assertTrue(verB.atLeast(verB));
Assert.assertTrue(verC.atLeast(verC));
}
/**
* Verifies that version order comparisons using atLeast() behave as
* required.
*/
@Test
public void testVersionCompare() {
testVersionCompare("VERSION_0_0_1", "VERSION_0_0_2", "VERSION_0_0_3");
testVersionCompare("VERSION_0_1_0", "VERSION_0_2_0", "VERSION_0_3_0");
testVersionCompare("VERSION_1_0_0", "VERSION_2_0_0", "VERSION_3_0_0");
testVersionCompare("VERSION_1_2_3", "VERSION_1_3_3", "VERSION_2_0_0");
}
/**
* Verifies that versions can be tested for equality using equals().
*/
@Test
public void testVersionEquals() {
GuacamoleProtocolVersion version;
version = GuacamoleProtocolVersion.parseVersion("VERSION_012_102_398");
Assert.assertTrue(version.equals(version));
Assert.assertTrue(version.equals(new GuacamoleProtocolVersion(12, 102, 398)));
Assert.assertFalse(version.equals(new GuacamoleProtocolVersion(12, 102, 399)));
Assert.assertFalse(version.equals(new GuacamoleProtocolVersion(12, 103, 398)));
Assert.assertFalse(version.equals(new GuacamoleProtocolVersion(11, 102, 398)));
version = GuacamoleProtocolVersion.parseVersion("VERSION_1_0_0");
Assert.assertTrue(version.equals(GuacamoleProtocolVersion.VERSION_1_0_0));
Assert.assertFalse(version.equals(GuacamoleProtocolVersion.VERSION_1_1_0));
version = GuacamoleProtocolVersion.parseVersion("VERSION_1_1_0");
Assert.assertTrue(version.equals(GuacamoleProtocolVersion.VERSION_1_1_0));
Assert.assertFalse(version.equals(GuacamoleProtocolVersion.VERSION_1_0_0));
}
/**
* Verifies that versions can be converted to their Guacamole protocol
* representation through calling toString().
*/
@Test
public void testToString() {
Assert.assertEquals("VERSION_1_0_0", GuacamoleProtocolVersion.VERSION_1_0_0.toString());
Assert.assertEquals("VERSION_1_1_0", GuacamoleProtocolVersion.VERSION_1_1_0.toString());
Assert.assertEquals("VERSION_12_103_398", new GuacamoleProtocolVersion(12, 103, 398).toString());
}
}

View File

@@ -0,0 +1,78 @@
/*
* 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 org.apache.guacamole.GuacamoleException;
import org.junit.Assert;
import org.junit.Test;
/**
* Unit test for GuacamoleStatus that verifies exception translation functions
* as required.
*/
public class GuacamoleStatusTest {
/**
* Verifies that the {@link SUCCESS} status code does NOT translate to a
* GuacamoleException, but instead throws an IllegalStateException.
*/
@Test
public void testSuccessHasNoException() {
try {
GuacamoleStatus.SUCCESS.toException("Test message");
Assert.fail("GuacamoleStatus.SUCCESS must throw "
+ "IllegalStateException for toException().");
}
catch (IllegalStateException e) {
// Expected
}
}
/**
* Verifies that each non-success GuacamoleStatus maps to a
* GuacamoleException associated with that GuacamoleStatus.
*/
@Test
public void testStatusExceptionMapping() {
for (GuacamoleStatus status : GuacamoleStatus.values()) {
// Ignore SUCCESS status (tested via testSuccessHasNoException())
if (status == GuacamoleStatus.SUCCESS)
continue;
String message = "Test message: " + status;
GuacamoleException e = status.toException(message);
Assert.assertEquals("toException() should return a "
+ "GuacamoleException that maps to the same status.",
status, e.getStatus());
Assert.assertEquals("toException() should return a "
+ "GuacamoleException that uses the provided message.",
message, e.getMessage());
}
}
}