Convert "guacamole-tunnel" web application into an equivalent Apache Guacamole extension.
3
.gitignore
vendored
@ -32,9 +32,6 @@
|
|||||||
/client/administration/installer/UDSAdminInstaller/MSChart.exe
|
/client/administration/installer/UDSAdminInstaller/MSChart.exe
|
||||||
/client/administration/installer/UDSAdminInstaller/UDSAdminSetup.exe
|
/client/administration/installer/UDSAdminInstaller/UDSAdminSetup.exe
|
||||||
|
|
||||||
# /guacamole-tunnel/
|
|
||||||
/guacamole-tunnel/target
|
|
||||||
|
|
||||||
# /linuxActor/
|
# /linuxActor/
|
||||||
/linuxActor/udsactor_*
|
/linuxActor/udsactor_*
|
||||||
|
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
*~
|
*~
|
||||||
|
|
||||||
# Generated files
|
# Generated files
|
||||||
src/main/webapp/META-INF/
|
|
||||||
src/main/webapp/generated/
|
|
||||||
target/
|
target/
|
||||||
|
|
||||||
# IDE-specific configuration
|
# IDE-specific configuration
|
94
guacamole-auth-uds/pom.xml
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
<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.openuds.server</groupId>
|
||||||
|
<artifactId>guacamole-auth-uds</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<version>2.5.0</version>
|
||||||
|
<name>UDS Integration Extension for Apache Guacamole</name>
|
||||||
|
<url>https://github.com/dkmstr/openuds</url>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
|
||||||
|
<!-- Compile using Java 1.8 -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.3</version>
|
||||||
|
<configuration>
|
||||||
|
<source>1.8</source>
|
||||||
|
<target>1.8</target>
|
||||||
|
<compilerArgs>
|
||||||
|
<arg>-Xlint:all</arg>
|
||||||
|
<arg>-Werror</arg>
|
||||||
|
</compilerArgs>
|
||||||
|
<fork>true</fork>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<!-- Copy dependencies prior to packaging -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-dependency-plugin</artifactId>
|
||||||
|
<version>2.10</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>unpack-dependencies</id>
|
||||||
|
<phase>prepare-package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>unpack-dependencies</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<includeScope>runtime</includeScope>
|
||||||
|
<outputDirectory>${project.build.directory}/classes</outputDirectory>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
<!-- Servlet API -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.servlet</groupId>
|
||||||
|
<artifactId>servlet-api</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
<version>2.5</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- JAX-RS API -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.ws.rs</groupId>
|
||||||
|
<artifactId>jsr311-api</artifactId>
|
||||||
|
<version>1.1.1</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Guacamole extension API -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.guacamole</groupId>
|
||||||
|
<artifactId>guacamole-ext</artifactId>
|
||||||
|
<version>1.1.0</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Guice - Dependency Injection -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.inject</groupId>
|
||||||
|
<artifactId>guice</artifactId>
|
||||||
|
<version>3.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020 Virtual Cable S.L.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
* are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||||
|
* may be used to endorse or promote products derived from this software
|
||||||
|
* without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.openuds.guacamole;
|
||||||
|
|
||||||
|
import org.apache.guacamole.net.auth.AbstractAuthenticatedUser;
|
||||||
|
import org.apache.guacamole.net.auth.AuthenticatedUser;
|
||||||
|
import org.apache.guacamole.net.auth.AuthenticationProvider;
|
||||||
|
import org.apache.guacamole.net.auth.Credentials;
|
||||||
|
import org.apache.guacamole.protocol.GuacamoleConfiguration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Guacamole user that was authenticated by an external UDS service.
|
||||||
|
*/
|
||||||
|
public class UDSAuthenticatedUser extends AbstractAuthenticatedUser {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The AuthenticationProvider that authenticated this user.
|
||||||
|
*/
|
||||||
|
private final AuthenticationProvider authProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The credentials provided by this user when they authenticated.
|
||||||
|
*/
|
||||||
|
private final Credentials credentials;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The GuacamoleConfiguration generated from the connection information
|
||||||
|
* returned by the external UDS service when the user authenticated.
|
||||||
|
*/
|
||||||
|
private final GuacamoleConfiguration config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new UDSAuthenticatedUser representing a Guacamole user that
|
||||||
|
* was authenticated by an external UDS service.
|
||||||
|
*
|
||||||
|
* @param authProvider
|
||||||
|
* The AuthenticationProvider that authenticated the user.
|
||||||
|
*
|
||||||
|
* @param credentials
|
||||||
|
* The credentials provided by the user when they authenticated.
|
||||||
|
*
|
||||||
|
* @param config
|
||||||
|
* The GuacamoleConfiguration generated from the connection information
|
||||||
|
* returned by the external UDS service when the user authenticated.
|
||||||
|
*/
|
||||||
|
public UDSAuthenticatedUser(AuthenticationProvider authProvider,
|
||||||
|
Credentials credentials, GuacamoleConfiguration config) {
|
||||||
|
this.authProvider = authProvider;
|
||||||
|
this.credentials = credentials;
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getIdentifier() {
|
||||||
|
return AuthenticatedUser.ANONYMOUS_IDENTIFIER;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthenticationProvider getAuthenticationProvider() {
|
||||||
|
return authProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Credentials getCredentials() {
|
||||||
|
return credentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the GuacamoleConfiguration generated from the connection
|
||||||
|
* information provided by the external UDS service when the user
|
||||||
|
* authenticated.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The GuacamoleConfiguration generated from the connection information
|
||||||
|
* provided by the external UDS service when the user authenticated.
|
||||||
|
*/
|
||||||
|
public GuacamoleConfiguration getGuacamoleConfiguration() {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,155 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020 Virtual Cable S.L.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
* are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||||
|
* may be used to endorse or promote products derived from this software
|
||||||
|
* without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.openuds.guacamole;
|
||||||
|
|
||||||
|
import com.google.inject.Guice;
|
||||||
|
import com.google.inject.Injector;
|
||||||
|
import java.util.Collections;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import org.apache.guacamole.GuacamoleException;
|
||||||
|
import org.apache.guacamole.GuacamoleServerException;
|
||||||
|
import org.apache.guacamole.form.Field;
|
||||||
|
import org.apache.guacamole.net.auth.AbstractAuthenticationProvider;
|
||||||
|
import org.apache.guacamole.net.auth.AuthenticatedUser;
|
||||||
|
import org.apache.guacamole.net.auth.Credentials;
|
||||||
|
import org.apache.guacamole.net.auth.UserContext;
|
||||||
|
import org.apache.guacamole.net.auth.credentials.CredentialsInfo;
|
||||||
|
import org.apache.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException;
|
||||||
|
import org.apache.guacamole.net.auth.simple.SimpleUserContext;
|
||||||
|
import org.apache.guacamole.protocol.GuacamoleConfiguration;
|
||||||
|
import org.openuds.guacamole.connection.ConnectionService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AuthenticationProvider implementation which authenticates users that are
|
||||||
|
* confirmed as authorized by an external UDS service.
|
||||||
|
*/
|
||||||
|
public class UDSAuthenticationProvider extends AbstractAuthenticationProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the single connection that should be exposed to any user
|
||||||
|
* that authenticates via UDS.
|
||||||
|
*/
|
||||||
|
private static final String CONNECTION_NAME = "UDS";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the query parameter that should contain the data sent to
|
||||||
|
* the UDS service for authentication.
|
||||||
|
*/
|
||||||
|
private static final String DATA_PARAMETER_NAME = "data";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logger for this class.
|
||||||
|
*/
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(UDSAuthenticationProvider.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service for retrieving connection configuration information from the
|
||||||
|
* UDS service.
|
||||||
|
*/
|
||||||
|
private final ConnectionService connectionService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new UDSAuthenticationProvider which authenticates users
|
||||||
|
* against an external UDS service.
|
||||||
|
*
|
||||||
|
* @throws GuacamoleException
|
||||||
|
* If an error prevents guacamole.properties from being read.
|
||||||
|
*/
|
||||||
|
public UDSAuthenticationProvider() throws GuacamoleException {
|
||||||
|
|
||||||
|
// Create an injector with OpenUDS- and Guacamole-specific services
|
||||||
|
// properly bound
|
||||||
|
Injector injector = Guice.createInjector(
|
||||||
|
new UDSModule()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Pull instance of connection service from injector
|
||||||
|
connectionService = injector.getInstance(ConnectionService.class);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getIdentifier() {
|
||||||
|
return "uds";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthenticatedUser authenticateUser(Credentials credentials)
|
||||||
|
throws GuacamoleException {
|
||||||
|
|
||||||
|
HttpServletRequest request = credentials.getRequest();
|
||||||
|
|
||||||
|
// Pull OpenUDS-specific "data" parameter
|
||||||
|
String data = request.getParameter(DATA_PARAMETER_NAME);
|
||||||
|
if (data != null && !data.isEmpty()) {
|
||||||
|
|
||||||
|
logger.debug("Retrieving connection configuration using data from \"{}\"...", data);
|
||||||
|
|
||||||
|
// Retrieve connection information using provided data
|
||||||
|
GuacamoleConfiguration config = connectionService.getConnectionConfiguration(data);
|
||||||
|
if (config != null) {
|
||||||
|
|
||||||
|
// Report successful authentication as a temporary, anonymous user,
|
||||||
|
// storing the retrieved connection configuration data for future use
|
||||||
|
return new UDSAuthenticatedUser(this, credentials, config);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Required parameter was missing or was invalid
|
||||||
|
throw new GuacamoleInvalidCredentialsException(
|
||||||
|
"Connection data was not provided or was rejected by UDS.",
|
||||||
|
new CredentialsInfo(Collections.<Field>singletonList(
|
||||||
|
new Field(DATA_PARAMETER_NAME, Field.Type.QUERY_PARAMETER)
|
||||||
|
))
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserContext getUserContext(AuthenticatedUser authenticatedUser)
|
||||||
|
throws GuacamoleException {
|
||||||
|
|
||||||
|
// Provide data only for users authenticated by this extension
|
||||||
|
if (!(authenticatedUser instanceof UDSAuthenticatedUser))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Expose a single connection (derived from the "data" parameter
|
||||||
|
// provided during authentication)
|
||||||
|
return new SimpleUserContext(this, Collections.singletonMap(
|
||||||
|
CONNECTION_NAME,
|
||||||
|
((UDSAuthenticatedUser) authenticatedUser).getGuacamoleConfiguration()
|
||||||
|
));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -28,9 +28,10 @@
|
|||||||
|
|
||||||
package org.openuds.guacamole;
|
package org.openuds.guacamole;
|
||||||
|
|
||||||
import com.google.inject.servlet.ServletModule;
|
import com.google.inject.AbstractModule;
|
||||||
import com.sun.jersey.guice.spi.container.servlet.GuiceContainer;
|
import org.apache.guacamole.GuacamoleException;
|
||||||
import org.apache.guacamole.tunnel.TunnelRequestService;
|
import org.apache.guacamole.environment.Environment;
|
||||||
|
import org.apache.guacamole.environment.LocalEnvironment;
|
||||||
import org.openuds.guacamole.config.ConfigurationService;
|
import org.openuds.guacamole.config.ConfigurationService;
|
||||||
import org.openuds.guacamole.connection.ConnectionService;
|
import org.openuds.guacamole.connection.ConnectionService;
|
||||||
|
|
||||||
@ -38,18 +39,34 @@ import org.openuds.guacamole.connection.ConnectionService;
|
|||||||
* Guice module which binds classes required by the OpenUDS integration of
|
* Guice module which binds classes required by the OpenUDS integration of
|
||||||
* Apache Guacamole.
|
* Apache Guacamole.
|
||||||
*/
|
*/
|
||||||
public class UDSModule extends ServletModule {
|
public class UDSModule extends AbstractModule {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Guacamole server environment.
|
||||||
|
*/
|
||||||
|
private final Environment environment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new UDSModule which binds classes required by the OpenUDS
|
||||||
|
* integration of Apache Guacamole, including an implementation of the
|
||||||
|
* Guacamole server {@link Environment}.
|
||||||
|
*
|
||||||
|
* @throws GuacamoleException
|
||||||
|
* If the guacamole.properties file cannot be read.
|
||||||
|
*/
|
||||||
|
public UDSModule() throws GuacamoleException {
|
||||||
|
this.environment = new LocalEnvironment();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void configureServlets() {
|
protected void configure() {
|
||||||
|
|
||||||
// Serve servlets, etc. with Guice
|
// Bind instance of Guacamole server environment
|
||||||
bind(GuiceContainer.class);
|
bind(Environment.class).toInstance(environment);
|
||||||
|
|
||||||
// Bind UDS-specific services
|
// Bind UDS-specific services
|
||||||
bind(ConfigurationService.class);
|
bind(ConfigurationService.class);
|
||||||
bind(ConnectionService.class);
|
bind(ConnectionService.class);
|
||||||
bind(TunnelRequestService.class);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -35,16 +35,16 @@ import java.io.FileReader;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.util.Properties;
|
|
||||||
import javax.servlet.ServletContext;
|
|
||||||
import org.apache.guacamole.GuacamoleException;
|
import org.apache.guacamole.GuacamoleException;
|
||||||
import org.apache.guacamole.GuacamoleServerException;
|
import org.apache.guacamole.GuacamoleServerException;
|
||||||
|
import org.apache.guacamole.environment.Environment;
|
||||||
|
import org.apache.guacamole.properties.StringGuacamoleProperty;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service that provides access to configuration information stored within
|
* Service that provides access to OpenUDS-specific configuration information
|
||||||
* OpenUDS' tunnel.properties file.
|
* stored within guacamole.properties.
|
||||||
*/
|
*/
|
||||||
@Singleton
|
@Singleton
|
||||||
public class ConfigurationService {
|
public class ConfigurationService {
|
||||||
@ -59,7 +59,14 @@ public class ConfigurationService {
|
|||||||
* whose content dictates the base URL of the service providing connection
|
* whose content dictates the base URL of the service providing connection
|
||||||
* configuration information.
|
* configuration information.
|
||||||
*/
|
*/
|
||||||
private static final String UDSFILE_PROPERTY = "udsfile";
|
private static final StringGuacamoleProperty UDSFILE_PROPERTY = new StringGuacamoleProperty() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "udsfile";
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The path beneath the OpenUDS service base URI (scheme + hostname) at
|
* The path beneath the OpenUDS service base URI (scheme + hostname) at
|
||||||
@ -69,9 +76,10 @@ public class ConfigurationService {
|
|||||||
private static final String UDS_CONNECTION_PATH = "/guacamole/";
|
private static final String UDS_CONNECTION_PATH = "/guacamole/";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The base URI (scheme + hostname) where OpenUDS is being served.
|
* The Guacamole server environment.
|
||||||
*/
|
*/
|
||||||
private final URI udsBaseURI;
|
@Inject
|
||||||
|
private Environment environment;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the contents of the given file, reading the URI of the OpenUDS
|
* Parses the contents of the given file, reading the URI of the OpenUDS
|
||||||
@ -157,56 +165,6 @@ public class ConfigurationService {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new ConfigurationService which provides access to the
|
|
||||||
* configuration information stored within the "/WEB-INF/tunnel.properties"
|
|
||||||
* file in the classpath. This file will be parsed immediately, but any
|
|
||||||
* resulting errors will simply be logged. If configuration information
|
|
||||||
* cannot be read, attempts to retrieve this information later through calls
|
|
||||||
* to the getters of this service will fail with appropriate exceptions.
|
|
||||||
*
|
|
||||||
* @param context
|
|
||||||
* The ServletContext associated with the servlet container which is
|
|
||||||
* serving this web application.
|
|
||||||
*/
|
|
||||||
@Inject
|
|
||||||
public ConfigurationService(ServletContext context) {
|
|
||||||
|
|
||||||
// Read tunnel.properties
|
|
||||||
Properties config = new Properties();
|
|
||||||
try {
|
|
||||||
config.load(context.getResourceAsStream("/WEB-INF/tunnel.properties"));
|
|
||||||
}
|
|
||||||
catch (IOException e) {
|
|
||||||
logger.error("Unable to read tunnel.properties.", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse URI from the UDS file (if defined)
|
|
||||||
URI parsedURI = null;
|
|
||||||
String udsFile = config.getProperty(UDSFILE_PROPERTY);
|
|
||||||
if (udsFile != null) {
|
|
||||||
|
|
||||||
// Attempt to parse base URI from the UDS file, logging any failures
|
|
||||||
try {
|
|
||||||
parsedURI = getBaseURI(readServiceURI(udsFile));
|
|
||||||
}
|
|
||||||
catch (GuacamoleException e) {
|
|
||||||
logger.error("OpenUDS service URI could not be parsed. This "
|
|
||||||
+ "web application WILL NOT FUNCTION.", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no UDS file is defined, web application startup has failed
|
|
||||||
else
|
|
||||||
logger.error("Property \"{}\" not found within tunnel.properties. "
|
|
||||||
+ "This web application WILL NOT FUNCTION.", UDSFILE_PROPERTY);
|
|
||||||
|
|
||||||
// Assign the parsed URI, which may be null
|
|
||||||
udsBaseURI = parsedURI;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the base URI of the OpenUDS service. All services providing data
|
* Returns the base URI of the OpenUDS service. All services providing data
|
||||||
* to this Guacamole integration are hosted beneath this base URI.
|
* to this Guacamole integration are hosted beneath this base URI.
|
||||||
@ -221,11 +179,11 @@ public class ConfigurationService {
|
|||||||
*/
|
*/
|
||||||
public URI getUDSBaseURI() throws GuacamoleException {
|
public URI getUDSBaseURI() throws GuacamoleException {
|
||||||
|
|
||||||
// Explicitly fail if the configuration was not successfully read
|
// Parse URI from the UDS file (if defined)
|
||||||
if (udsBaseURI == null)
|
String udsFile = environment.getRequiredProperty(UDSFILE_PROPERTY);
|
||||||
throw new GuacamoleServerException("The UDS base URI is not defined.");
|
|
||||||
|
|
||||||
return udsBaseURI;
|
// Attempt to parse base URI from the UDS file
|
||||||
|
return getBaseURI(readServiceURI(udsFile));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
18
guacamole-auth-uds/src/main/resources/guac-manifest.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
"guacamoleVersion" : "1.1.0",
|
||||||
|
|
||||||
|
"name" : "UDS Integration Extension for Apache Guacamole",
|
||||||
|
"namespace" : "uds",
|
||||||
|
"largeIcon" : "images/udsicon.png",
|
||||||
|
"smallIcon" : "images/udsicon.png",
|
||||||
|
|
||||||
|
"authProviders" : [
|
||||||
|
"org.openuds.guacamole.UDSAuthenticationProvider"
|
||||||
|
],
|
||||||
|
|
||||||
|
"translations" : [
|
||||||
|
"translations/en.json"
|
||||||
|
]
|
||||||
|
|
||||||
|
}
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
"DATA_SOURCE_UDS" : {
|
||||||
|
"NAME" : "UDS"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Before Width: | Height: | Size: 586 B After Width: | Height: | Size: 586 B |
Before Width: | Height: | Size: 512 B After Width: | Height: | Size: 512 B |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 611 B After Width: | Height: | Size: 611 B |
Before Width: | Height: | Size: 690 B After Width: | Height: | Size: 690 B |
Before Width: | Height: | Size: 525 B After Width: | Height: | Size: 525 B |
Before Width: | Height: | Size: 707 B After Width: | Height: | Size: 707 B |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 560 B After Width: | Height: | Size: 560 B |
Before Width: | Height: | Size: 626 B After Width: | Height: | Size: 626 B |
Before Width: | Height: | Size: 648 B After Width: | Height: | Size: 648 B |
Before Width: | Height: | Size: 810 B After Width: | Height: | Size: 810 B |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 843 B After Width: | Height: | Size: 843 B |
Before Width: | Height: | Size: 717 B After Width: | Height: | Size: 717 B |
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 9.0 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 326 B After Width: | Height: | Size: 326 B |
Before Width: | Height: | Size: 71 B After Width: | Height: | Size: 71 B |
Before Width: | Height: | Size: 72 B After Width: | Height: | Size: 72 B |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 473 B After Width: | Height: | Size: 473 B |
Before Width: | Height: | Size: 691 B After Width: | Height: | Size: 691 B |
Before Width: | Height: | Size: 727 B After Width: | Height: | Size: 727 B |
Before Width: | Height: | Size: 792 B After Width: | Height: | Size: 792 B |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
@ -1,5 +0,0 @@
|
|||||||
Apache Guacamole
|
|
||||||
Copyright 2016 The Apache Software Foundation
|
|
||||||
|
|
||||||
This product includes software developed at
|
|
||||||
The Apache Software Foundation (http://www.apache.org/).
|
|
@ -1,170 +0,0 @@
|
|||||||
<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.openuds.server</groupId>
|
|
||||||
<artifactId>transport</artifactId>
|
|
||||||
<packaging>war</packaging>
|
|
||||||
<version>2.5.0</version>
|
|
||||||
<name>Guacamole Transport</name>
|
|
||||||
<url>https://github.com/dkmstr/openuds</url>
|
|
||||||
|
|
||||||
<properties>
|
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
|
||||||
</properties>
|
|
||||||
|
|
||||||
<build>
|
|
||||||
<finalName>transport</finalName>
|
|
||||||
<plugins>
|
|
||||||
|
|
||||||
<!-- Compile using Java 1.6 -->
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
|
||||||
<version>3.3</version>
|
|
||||||
<configuration>
|
|
||||||
<source>1.6</source>
|
|
||||||
<target>1.6</target>
|
|
||||||
<compilerArgs>
|
|
||||||
<arg>-Xlint:all</arg>
|
|
||||||
<arg>-Werror</arg>
|
|
||||||
</compilerArgs>
|
|
||||||
<fork>true</fork>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
|
|
||||||
<!-- Overlay guacamole-common-js (zip) -->
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-war-plugin</artifactId>
|
|
||||||
<version>2.6</version>
|
|
||||||
<configuration>
|
|
||||||
<overlays>
|
|
||||||
<overlay>
|
|
||||||
<groupId>org.apache.guacamole</groupId>
|
|
||||||
<artifactId>guacamole-common-js</artifactId>
|
|
||||||
<type>zip</type>
|
|
||||||
</overlay>
|
|
||||||
</overlays>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
|
|
||||||
</plugins>
|
|
||||||
|
|
||||||
</build>
|
|
||||||
<dependencies>
|
|
||||||
|
|
||||||
<!-- Servlet API -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>javax.servlet</groupId>
|
|
||||||
<artifactId>servlet-api</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
<version>2.5</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- SLF4J - logging -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.slf4j</groupId>
|
|
||||||
<artifactId>slf4j-api</artifactId>
|
|
||||||
<version>1.6.1</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.slf4j</groupId>
|
|
||||||
<artifactId>slf4j-jcl</artifactId>
|
|
||||||
<version>1.6.1</version>
|
|
||||||
<scope>runtime</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- Main Guacamole library -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.guacamole</groupId>
|
|
||||||
<artifactId>guacamole-common</artifactId>
|
|
||||||
<version>1.1.0</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- Guacamole JavaScript library -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.guacamole</groupId>
|
|
||||||
<artifactId>guacamole-common-js</artifactId>
|
|
||||||
<version>1.2.0</version>
|
|
||||||
<type>zip</type>
|
|
||||||
<scope>runtime</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- JSR 356 WebSocket API -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>javax.websocket</groupId>
|
|
||||||
<artifactId>javax.websocket-api</artifactId>
|
|
||||||
<version>1.0</version>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- Jetty 8 servlet API (websocket) -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
|
||||||
<artifactId>jetty-websocket</artifactId>
|
|
||||||
<version>8.1.1.v20120215</version>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- Jetty 9.0 servlet API (websocket) -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
|
||||||
<artifactId>jetty-parent</artifactId>
|
|
||||||
<version>20</version>
|
|
||||||
<scope>provided</scope>
|
|
||||||
<type>pom</type>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
|
||||||
<artifactId>websocket-api</artifactId>
|
|
||||||
<version>9.0.7.v20131107</version>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
|
||||||
<artifactId>websocket-servlet</artifactId>
|
|
||||||
<version>9.0.7.v20131107</version>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- Tomcat servlet API (websocket) -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.tomcat</groupId>
|
|
||||||
<artifactId>tomcat-catalina</artifactId>
|
|
||||||
<version>7.0.37</version>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.tomcat</groupId>
|
|
||||||
<artifactId>tomcat-coyote</artifactId>
|
|
||||||
<version>7.0.37</version>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- Jersey - Guice extension -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.sun.jersey.contribs</groupId>
|
|
||||||
<artifactId>jersey-guice</artifactId>
|
|
||||||
<version>1.17.1</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- Guice Servlet -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.google.inject.extensions</groupId>
|
|
||||||
<artifactId>guice-servlet</artifactId>
|
|
||||||
<version>3.0</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- Guice - Dependency Injection -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.google.inject</groupId>
|
|
||||||
<artifactId>guice</artifactId>
|
|
||||||
<version>3.0</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
</project>
|
|
@ -1,41 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.tunnel;
|
|
||||||
|
|
||||||
import com.google.inject.Module;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generic means of loading a tunnel without adding explicit dependencies within
|
|
||||||
* the main ServletModule, as not all servlet containers may have the classes
|
|
||||||
* required by all tunnel implementations.
|
|
||||||
*
|
|
||||||
* @author Michael Jumper
|
|
||||||
*/
|
|
||||||
public interface TunnelLoader extends Module {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether this type of tunnel is supported by the servlet container.
|
|
||||||
*
|
|
||||||
* @return true if this type of tunnel is supported and can be loaded
|
|
||||||
* without errors, false otherwise.
|
|
||||||
*/
|
|
||||||
public boolean isSupported();
|
|
||||||
|
|
||||||
}
|
|
@ -1,116 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file has been modified from the original, upstream version to facilitate
|
|
||||||
* integration with OpenUDS.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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.tunnel;
|
|
||||||
|
|
||||||
import org.apache.guacamole.tunnel.http.RestrictedGuacamoleHTTPTunnelServlet;
|
|
||||||
import com.google.inject.servlet.ServletModule;
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module which loads tunnel implementations.
|
|
||||||
*
|
|
||||||
* @author Michael Jumper
|
|
||||||
*/
|
|
||||||
public class TunnelModule extends ServletModule {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logger for this class.
|
|
||||||
*/
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(TunnelModule.class);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Classnames of all implementation-specific WebSocket tunnel modules.
|
|
||||||
*/
|
|
||||||
private static final String[] WEBSOCKET_MODULES = {
|
|
||||||
"org.apache.guacamole.tunnel.websocket.WebSocketTunnelModule",
|
|
||||||
"org.apache.guacamole.tunnel.websocket.jetty8.WebSocketTunnelModule",
|
|
||||||
"org.apache.guacamole.tunnel.websocket.jetty9.WebSocketTunnelModule",
|
|
||||||
"org.apache.guacamole.tunnel.websocket.tomcat.WebSocketTunnelModule"
|
|
||||||
};
|
|
||||||
|
|
||||||
private boolean loadWebSocketModule(String classname) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
// Attempt to find WebSocket module
|
|
||||||
Class<?> module = Class.forName(classname);
|
|
||||||
|
|
||||||
// Create loader
|
|
||||||
TunnelLoader loader = (TunnelLoader) module.getConstructor().newInstance();
|
|
||||||
|
|
||||||
// Install module, if supported
|
|
||||||
if (loader.isSupported()) {
|
|
||||||
install(loader);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no such class or constructor, etc., then this particular
|
|
||||||
// WebSocket support is not present
|
|
||||||
catch (ClassNotFoundException e) {}
|
|
||||||
catch (NoClassDefFoundError e) {}
|
|
||||||
catch (NoSuchMethodException e) {}
|
|
||||||
|
|
||||||
// Log errors which indicate bugs
|
|
||||||
catch (InstantiationException e) {
|
|
||||||
logger.debug("Error instantiating WebSocket module.", e);
|
|
||||||
}
|
|
||||||
catch (IllegalAccessException e) {
|
|
||||||
logger.debug("Error instantiating WebSocket module.", e);
|
|
||||||
}
|
|
||||||
catch (InvocationTargetException e) {
|
|
||||||
logger.debug("Error instantiating WebSocket module.", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load attempt failed
|
|
||||||
return false;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void configureServlets() {
|
|
||||||
|
|
||||||
bind(TunnelRequestService.class);
|
|
||||||
|
|
||||||
// Set up HTTP tunnel
|
|
||||||
serve("/tunnel").with(RestrictedGuacamoleHTTPTunnelServlet.class);
|
|
||||||
|
|
||||||
// Try to load each WebSocket tunnel in sequence
|
|
||||||
for (String classname : WEBSOCKET_MODULES) {
|
|
||||||
if (loadWebSocketModule(classname)) {
|
|
||||||
logger.debug("WebSocket module loaded: {}", classname);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warn of lack of WebSocket
|
|
||||||
logger.info("WebSocket support NOT present. Only HTTP will be used.");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,371 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.tunnel;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import org.apache.guacamole.GuacamoleClientException;
|
|
||||||
import org.apache.guacamole.GuacamoleClientException;
|
|
||||||
import org.apache.guacamole.GuacamoleException;
|
|
||||||
import org.apache.guacamole.GuacamoleException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A request object which provides only the functions absolutely required to
|
|
||||||
* retrieve and connect to a tunnel.
|
|
||||||
*
|
|
||||||
* @author Michael Jumper
|
|
||||||
*/
|
|
||||||
public abstract class TunnelRequest {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name of the request parameter containing the user's authentication
|
|
||||||
* token.
|
|
||||||
*/
|
|
||||||
public static final String AUTH_TOKEN_PARAMETER = "token";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name of the parameter containing the identifier of the
|
|
||||||
* AuthenticationProvider associated with the UserContext containing the
|
|
||||||
* object to which a tunnel is being requested.
|
|
||||||
*/
|
|
||||||
public static final String AUTH_PROVIDER_IDENTIFIER_PARAMETER = "GUAC_DATA_SOURCE";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name of the parameter specifying the type of object to which a
|
|
||||||
* tunnel is being requested. Currently, this may be "c" for a Guacamole
|
|
||||||
* connection, or "g" for a Guacamole connection group.
|
|
||||||
*/
|
|
||||||
public static final String TYPE_PARAMETER = "GUAC_TYPE";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name of the parameter containing the unique identifier of the object
|
|
||||||
* to which a tunnel is being requested.
|
|
||||||
*/
|
|
||||||
public static final String IDENTIFIER_PARAMETER = "GUAC_ID";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name of the parameter containing the desired display width, in
|
|
||||||
* pixels.
|
|
||||||
*/
|
|
||||||
public static final String WIDTH_PARAMETER = "GUAC_WIDTH";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name of the parameter containing the desired display height, in
|
|
||||||
* pixels.
|
|
||||||
*/
|
|
||||||
public static final String HEIGHT_PARAMETER = "GUAC_HEIGHT";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name of the parameter containing the desired display resolution, in
|
|
||||||
* DPI.
|
|
||||||
*/
|
|
||||||
public static final String DPI_PARAMETER = "GUAC_DPI";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name of the parameter specifying one supported audio mimetype. This
|
|
||||||
* will normally appear multiple times within a single tunnel request -
|
|
||||||
* once for each mimetype.
|
|
||||||
*/
|
|
||||||
public static final String AUDIO_PARAMETER = "GUAC_AUDIO";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name of the parameter specifying one supported video mimetype. This
|
|
||||||
* will normally appear multiple times within a single tunnel request -
|
|
||||||
* once for each mimetype.
|
|
||||||
*/
|
|
||||||
public static final String VIDEO_PARAMETER = "GUAC_VIDEO";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name of the parameter specifying one supported image mimetype. This
|
|
||||||
* will normally appear multiple times within a single tunnel request -
|
|
||||||
* once for each mimetype.
|
|
||||||
*/
|
|
||||||
public static final String IMAGE_PARAMETER = "GUAC_IMAGE";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All supported object types that can be used as the destination of a
|
|
||||||
* tunnel.
|
|
||||||
*/
|
|
||||||
public static enum Type {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Guacamole connection.
|
|
||||||
*/
|
|
||||||
CONNECTION("c"),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Guacamole connection group.
|
|
||||||
*/
|
|
||||||
CONNECTION_GROUP("g");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The parameter value which denotes a destination object of this type.
|
|
||||||
*/
|
|
||||||
final String PARAMETER_VALUE;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines a Type having the given corresponding parameter value.
|
|
||||||
*
|
|
||||||
* @param value
|
|
||||||
* The parameter value which denotes a destination object of this
|
|
||||||
* type.
|
|
||||||
*/
|
|
||||||
Type(String value) {
|
|
||||||
PARAMETER_VALUE = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the value of the parameter having the given name.
|
|
||||||
*
|
|
||||||
* @param name
|
|
||||||
* The name of the parameter to return.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* The value of the parameter having the given name, or null if no such
|
|
||||||
* parameter was specified.
|
|
||||||
*/
|
|
||||||
public abstract String getParameter(String name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a list of all values specified for the given parameter.
|
|
||||||
*
|
|
||||||
* @param name
|
|
||||||
* The name of the parameter to return.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* All values of the parameter having the given name , or null if no
|
|
||||||
* such parameter was specified.
|
|
||||||
*/
|
|
||||||
public abstract List<String> getParameterValues(String name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the value of the parameter having the given name, throwing an
|
|
||||||
* exception if the parameter is missing.
|
|
||||||
*
|
|
||||||
* @param name
|
|
||||||
* The name of the parameter to return.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* The value of the parameter having the given name.
|
|
||||||
*
|
|
||||||
* @throws GuacamoleException
|
|
||||||
* If the parameter is not present in the request.
|
|
||||||
*/
|
|
||||||
public String getRequiredParameter(String name) throws GuacamoleException {
|
|
||||||
|
|
||||||
// Pull requested parameter, aborting if absent
|
|
||||||
String value = getParameter(name);
|
|
||||||
if (value == null)
|
|
||||||
throw new GuacamoleClientException("Parameter \"" + name + "\" is required.");
|
|
||||||
|
|
||||||
return value;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the integer value of the parameter having the given name,
|
|
||||||
* throwing an exception if the parameter cannot be parsed.
|
|
||||||
*
|
|
||||||
* @param name
|
|
||||||
* The name of the parameter to return.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* The integer value of the parameter having the given name, or null if
|
|
||||||
* the parameter is missing.
|
|
||||||
*
|
|
||||||
* @throws GuacamoleException
|
|
||||||
* If the parameter is not a valid integer.
|
|
||||||
*/
|
|
||||||
public Integer getIntegerParameter(String name) throws GuacamoleException {
|
|
||||||
|
|
||||||
// Pull requested parameter
|
|
||||||
String value = getParameter(name);
|
|
||||||
if (value == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
// Attempt to parse as an integer
|
|
||||||
try {
|
|
||||||
return Integer.parseInt(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rethrow any parsing error as a GuacamoleClientException
|
|
||||||
catch (NumberFormatException e) {
|
|
||||||
throw new GuacamoleClientException("Parameter \"" + name + "\" must be a valid integer.", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the authentication token associated with this tunnel request.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* The authentication token associated with this tunnel request, or
|
|
||||||
* null if no authentication token is present.
|
|
||||||
*/
|
|
||||||
public String getAuthenticationToken() {
|
|
||||||
return getParameter(AUTH_TOKEN_PARAMETER);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the identifier of the AuthenticationProvider associated with the
|
|
||||||
* UserContext from which the connection or connection group is to be
|
|
||||||
* retrieved when the tunnel is created. In the context of the REST API and
|
|
||||||
* the JavaScript side of the web application, this is referred to as the
|
|
||||||
* data source identifier.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* The identifier of the AuthenticationProvider associated with the
|
|
||||||
* UserContext from which the connection or connection group is to be
|
|
||||||
* retrieved when the tunnel is created.
|
|
||||||
*
|
|
||||||
* @throws GuacamoleException
|
|
||||||
* If the identifier was not present in the request.
|
|
||||||
*/
|
|
||||||
public String getAuthenticationProviderIdentifier()
|
|
||||||
throws GuacamoleException {
|
|
||||||
return getRequiredParameter(AUTH_PROVIDER_IDENTIFIER_PARAMETER);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the type of object for which the tunnel is being requested.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* The type of object for which the tunnel is being requested.
|
|
||||||
*
|
|
||||||
* @throws GuacamoleException
|
|
||||||
* If the type was not present in the request, or if the type requested
|
|
||||||
* is in the wrong format.
|
|
||||||
*/
|
|
||||||
public Type getType() throws GuacamoleException {
|
|
||||||
|
|
||||||
String type = getRequiredParameter(TYPE_PARAMETER);
|
|
||||||
|
|
||||||
// For each possible object type
|
|
||||||
for (Type possibleType : Type.values()) {
|
|
||||||
|
|
||||||
// Match against defined parameter value
|
|
||||||
if (type.equals(possibleType.PARAMETER_VALUE))
|
|
||||||
return possibleType;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new GuacamoleClientException("Illegal identifier - unknown type.");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the identifier of the destination of the tunnel being requested.
|
|
||||||
* As there are multiple types of destination objects available, and within
|
|
||||||
* multiple data sources, the associated object type and data source are
|
|
||||||
* also necessary to determine what this identifier refers to.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* The identifier of the destination of the tunnel being requested.
|
|
||||||
*
|
|
||||||
* @throws GuacamoleException
|
|
||||||
* If the identifier was not present in the request.
|
|
||||||
*/
|
|
||||||
public String getIdentifier() throws GuacamoleException {
|
|
||||||
return getRequiredParameter(IDENTIFIER_PARAMETER);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the display width desired for the Guacamole session over the
|
|
||||||
* tunnel being requested.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* The display width desired for the Guacamole session over the tunnel
|
|
||||||
* being requested, or null if no width was given.
|
|
||||||
*
|
|
||||||
* @throws GuacamoleException
|
|
||||||
* If the width specified was not a valid integer.
|
|
||||||
*/
|
|
||||||
public Integer getWidth() throws GuacamoleException {
|
|
||||||
return getIntegerParameter(WIDTH_PARAMETER);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the display height desired for the Guacamole session over the
|
|
||||||
* tunnel being requested.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* The display height desired for the Guacamole session over the tunnel
|
|
||||||
* being requested, or null if no width was given.
|
|
||||||
*
|
|
||||||
* @throws GuacamoleException
|
|
||||||
* If the height specified was not a valid integer.
|
|
||||||
*/
|
|
||||||
public Integer getHeight() throws GuacamoleException {
|
|
||||||
return getIntegerParameter(HEIGHT_PARAMETER);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the display resolution desired for the Guacamole session over
|
|
||||||
* the tunnel being requested, in DPI.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* The display resolution desired for the Guacamole session over the
|
|
||||||
* tunnel being requested, or null if no resolution was given.
|
|
||||||
*
|
|
||||||
* @throws GuacamoleException
|
|
||||||
* If the resolution specified was not a valid integer.
|
|
||||||
*/
|
|
||||||
public Integer getDPI() throws GuacamoleException {
|
|
||||||
return getIntegerParameter(DPI_PARAMETER);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a list of all audio mimetypes declared as supported within the
|
|
||||||
* tunnel request.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* A list of all audio mimetypes declared as supported within the
|
|
||||||
* tunnel request, or null if no mimetypes were specified.
|
|
||||||
*/
|
|
||||||
public List<String> getAudioMimetypes() {
|
|
||||||
return getParameterValues(AUDIO_PARAMETER);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a list of all video mimetypes declared as supported within the
|
|
||||||
* tunnel request.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* A list of all video mimetypes declared as supported within the
|
|
||||||
* tunnel request, or null if no mimetypes were specified.
|
|
||||||
*/
|
|
||||||
public List<String> getVideoMimetypes() {
|
|
||||||
return getParameterValues(VIDEO_PARAMETER);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a list of all image mimetypes declared as supported within the
|
|
||||||
* tunnel request.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* A list of all image mimetypes declared as supported within the
|
|
||||||
* tunnel request, or null if no mimetypes were specified.
|
|
||||||
*/
|
|
||||||
public List<String> getImageMimetypes() {
|
|
||||||
return getParameterValues(IMAGE_PARAMETER);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,159 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file has been modified from the original, upstream version to facilitate
|
|
||||||
* integration with OpenUDS.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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.tunnel;
|
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
|
||||||
import com.google.inject.Singleton;
|
|
||||||
import java.util.List;
|
|
||||||
import org.apache.guacamole.GuacamoleClientException;
|
|
||||||
import org.apache.guacamole.GuacamoleException;
|
|
||||||
import org.apache.guacamole.net.GuacamoleSocket;
|
|
||||||
import org.apache.guacamole.net.GuacamoleTunnel;
|
|
||||||
import org.apache.guacamole.net.InetGuacamoleSocket;
|
|
||||||
import org.apache.guacamole.net.SimpleGuacamoleTunnel;
|
|
||||||
import org.apache.guacamole.protocol.ConfiguredGuacamoleSocket;
|
|
||||||
import org.apache.guacamole.protocol.GuacamoleClientInformation;
|
|
||||||
import org.apache.guacamole.protocol.GuacamoleConfiguration;
|
|
||||||
import org.openuds.guacamole.connection.ConnectionService;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility class that takes a standard request from the Guacamole JavaScript
|
|
||||||
* client and produces the corresponding GuacamoleTunnel. The implementation
|
|
||||||
* of this utility is specific to the form of request used by the upstream
|
|
||||||
* Guacamole web application, and is not necessarily useful to applications
|
|
||||||
* that use purely the Guacamole API.
|
|
||||||
*
|
|
||||||
* @author Michael Jumper
|
|
||||||
* @author Vasily Loginov
|
|
||||||
*/
|
|
||||||
@Singleton
|
|
||||||
public class TunnelRequestService {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logger for this class.
|
|
||||||
*/
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(TunnelRequestService.class);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Service for retrieving remotely-maintained connection information.
|
|
||||||
*/
|
|
||||||
@Inject
|
|
||||||
private ConnectionService connectionService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The hostname of the server hosting guacd.
|
|
||||||
*/
|
|
||||||
private static final String GUACD_HOSTNAME = "127.0.0.1";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The port that guacd will be listening on.
|
|
||||||
*/
|
|
||||||
private static final int GUACD_PORT = 4822;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new tunnel using the parameters and credentials present in
|
|
||||||
* the given request.
|
|
||||||
*
|
|
||||||
* @param request
|
|
||||||
* The HttpServletRequest describing the tunnel to create.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* The created tunnel, or null if the tunnel could not be created.
|
|
||||||
*
|
|
||||||
* @throws GuacamoleException
|
|
||||||
* If an error occurs while creating the tunnel.
|
|
||||||
*/
|
|
||||||
public GuacamoleTunnel createTunnel(TunnelRequest request) throws GuacamoleException {
|
|
||||||
|
|
||||||
// Pull OpenUDS-specific "data" parameter
|
|
||||||
String data = request.getParameter("data");
|
|
||||||
if (data == null || data.isEmpty()) {
|
|
||||||
logger.debug("No ID received in tunnel connect request.");
|
|
||||||
throw new GuacamoleClientException("Connection data not provided.");
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug("Establishing tunnel and connection with data from \"{}\"...", data);
|
|
||||||
|
|
||||||
// Get connection from remote service
|
|
||||||
GuacamoleConfiguration config = connectionService.getConnectionConfiguration(data);
|
|
||||||
if (config == null)
|
|
||||||
throw new GuacamoleClientException("Connection configuration could not be retrieved.");
|
|
||||||
|
|
||||||
// Get client information
|
|
||||||
GuacamoleClientInformation info = new GuacamoleClientInformation();
|
|
||||||
|
|
||||||
// Set width if provided
|
|
||||||
String width = request.getParameter("GUAC_WIDTH");
|
|
||||||
if (width != null)
|
|
||||||
info.setOptimalScreenWidth(Integer.parseInt(width));
|
|
||||||
|
|
||||||
// Set height if provided
|
|
||||||
String height = request.getParameter("GUAC_HEIGHT");
|
|
||||||
if (height != null)
|
|
||||||
info.setOptimalScreenHeight(Integer.parseInt(height));
|
|
||||||
|
|
||||||
// Set resolution if provided
|
|
||||||
String dpi = request.getParameter("GUAC_DPI");
|
|
||||||
if (dpi != null)
|
|
||||||
info.setOptimalResolution(Integer.parseInt(dpi));
|
|
||||||
|
|
||||||
// Add audio mimetypes
|
|
||||||
List<String> audio_mimetypes = request.getParameterValues("GUAC_AUDIO");
|
|
||||||
if (audio_mimetypes != null)
|
|
||||||
info.getAudioMimetypes().addAll(audio_mimetypes);
|
|
||||||
|
|
||||||
// Add video mimetypes
|
|
||||||
List<String> video_mimetypes = request.getParameterValues("GUAC_VIDEO");
|
|
||||||
if (video_mimetypes != null)
|
|
||||||
info.getVideoMimetypes().addAll(video_mimetypes);
|
|
||||||
|
|
||||||
// Add image mimetypes
|
|
||||||
List<String> image_mimetypes = request.getParameterValues("GUAC_IMAGE");
|
|
||||||
if (image_mimetypes != null)
|
|
||||||
info.getImageMimetypes().addAll(image_mimetypes);
|
|
||||||
|
|
||||||
// Connect socket for connection
|
|
||||||
GuacamoleSocket socket;
|
|
||||||
try {
|
|
||||||
socket = new ConfiguredGuacamoleSocket(
|
|
||||||
new InetGuacamoleSocket(GUACD_HOSTNAME, GUACD_PORT),
|
|
||||||
config, info
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log any errors during connection
|
|
||||||
catch (GuacamoleException e) {
|
|
||||||
logger.error("Unable to connect to guacd.", e);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return corresponding tunnel
|
|
||||||
return new SimpleGuacamoleTunnel(socket);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,88 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.tunnel.http;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import org.apache.guacamole.tunnel.TunnelRequest;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HTTP-specific implementation of TunnelRequest.
|
|
||||||
*
|
|
||||||
* @author Michael Jumper
|
|
||||||
*/
|
|
||||||
public class HTTPTunnelRequest extends TunnelRequest {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A copy of the parameters obtained from the HttpServletRequest used to
|
|
||||||
* construct the HTTPTunnelRequest.
|
|
||||||
*/
|
|
||||||
private final Map<String, List<String>> parameterMap =
|
|
||||||
new HashMap<String, List<String>>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a HTTPTunnelRequest which copies and exposes the parameters
|
|
||||||
* from the given HttpServletRequest.
|
|
||||||
*
|
|
||||||
* @param request
|
|
||||||
* The HttpServletRequest to copy parameter values from.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked") // getParameterMap() is defined as returning Map<String, String[]>
|
|
||||||
public HTTPTunnelRequest(HttpServletRequest request) {
|
|
||||||
|
|
||||||
// For each parameter
|
|
||||||
for (Map.Entry<String, String[]> mapEntry : ((Map<String, String[]>)
|
|
||||||
request.getParameterMap()).entrySet()) {
|
|
||||||
|
|
||||||
// Get parameter name and corresponding values
|
|
||||||
String parameterName = mapEntry.getKey();
|
|
||||||
List<String> parameterValues = Arrays.asList(mapEntry.getValue());
|
|
||||||
|
|
||||||
// Store copy of all values in our own map
|
|
||||||
parameterMap.put(
|
|
||||||
parameterName,
|
|
||||||
new ArrayList<String>(parameterValues)
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getParameter(String name) {
|
|
||||||
List<String> values = getParameterValues(name);
|
|
||||||
|
|
||||||
// Return the first value from the list if available
|
|
||||||
if (values != null && !values.isEmpty())
|
|
||||||
return values.get(0);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<String> getParameterValues(String name) {
|
|
||||||
return parameterMap.get(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,70 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file has been modified from the original, upstream version to facilitate
|
|
||||||
* integration with OpenUDS.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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.tunnel.http;
|
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
|
||||||
import com.google.inject.Singleton;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import org.apache.guacamole.GuacamoleException;
|
|
||||||
import org.apache.guacamole.net.GuacamoleTunnel;
|
|
||||||
import org.apache.guacamole.servlet.GuacamoleHTTPTunnelServlet;
|
|
||||||
import org.apache.guacamole.tunnel.TunnelRequestService;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connects users to a tunnel associated with the authorized connection
|
|
||||||
* having the given ID.
|
|
||||||
*
|
|
||||||
* @author Michael Jumper
|
|
||||||
*/
|
|
||||||
@Singleton
|
|
||||||
public class RestrictedGuacamoleHTTPTunnelServlet extends GuacamoleHTTPTunnelServlet {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Service for handling tunnel requests.
|
|
||||||
*/
|
|
||||||
@Inject
|
|
||||||
private TunnelRequestService tunnelRequestService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logger for this class.
|
|
||||||
*/
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(RestrictedGuacamoleHTTPTunnelServlet.class);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected GuacamoleTunnel doConnect(HttpServletRequest request) throws GuacamoleException {
|
|
||||||
|
|
||||||
// Attempt to create HTTP tunnel
|
|
||||||
GuacamoleTunnel tunnel = tunnelRequestService.createTunnel(new HTTPTunnelRequest(request));
|
|
||||||
|
|
||||||
// If successful, warn of lack of WebSocket
|
|
||||||
logger.info("Using HTTP tunnel (not WebSocket). Performance may be sub-optimal.");
|
|
||||||
|
|
||||||
return tunnel;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 leverage Guacamole's built-in HTTP tunnel implementation.
|
|
||||||
*/
|
|
||||||
package org.apache.guacamole.tunnel.http;
|
|
@ -1,23 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 are common to all tunnel implementations.
|
|
||||||
*/
|
|
||||||
package org.apache.guacamole.tunnel;
|
|
@ -1,122 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file has been modified from the original, upstream version to facilitate
|
|
||||||
* integration with OpenUDS.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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.tunnel.websocket;
|
|
||||||
|
|
||||||
import com.google.inject.Provider;
|
|
||||||
import java.util.Map;
|
|
||||||
import javax.websocket.EndpointConfig;
|
|
||||||
import javax.websocket.HandshakeResponse;
|
|
||||||
import javax.websocket.Session;
|
|
||||||
import javax.websocket.server.HandshakeRequest;
|
|
||||||
import javax.websocket.server.ServerEndpointConfig;
|
|
||||||
import org.apache.guacamole.GuacamoleException;
|
|
||||||
import org.apache.guacamole.net.GuacamoleTunnel;
|
|
||||||
import org.apache.guacamole.tunnel.TunnelRequest;
|
|
||||||
import org.apache.guacamole.websocket.GuacamoleWebSocketTunnelEndpoint;
|
|
||||||
import org.apache.guacamole.tunnel.TunnelRequestService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tunnel implementation which uses WebSocket as a tunnel backend, rather than
|
|
||||||
* HTTP, properly parsing connection IDs included in the connection request.
|
|
||||||
*/
|
|
||||||
public class RestrictedGuacamoleWebSocketTunnelEndpoint extends GuacamoleWebSocketTunnelEndpoint {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unique string which shall be used to store the TunnelRequest
|
|
||||||
* associated with a WebSocket connection.
|
|
||||||
*/
|
|
||||||
private static final String TUNNEL_REQUEST_PROPERTY = "WS_GUAC_TUNNEL_REQUEST";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unique string which shall be used to store the TunnelRequestService to
|
|
||||||
* be used for processing TunnelRequests.
|
|
||||||
*/
|
|
||||||
private static final String TUNNEL_REQUEST_SERVICE_PROPERTY = "WS_GUAC_TUNNEL_REQUEST_SERVICE";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configurator implementation which stores the requested GuacamoleTunnel
|
|
||||||
* within the user properties. The GuacamoleTunnel will be later retrieved
|
|
||||||
* during the connection process.
|
|
||||||
*/
|
|
||||||
public static class Configurator extends ServerEndpointConfig.Configurator {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provider which provides instances of a service for handling
|
|
||||||
* tunnel requests.
|
|
||||||
*/
|
|
||||||
private final Provider<TunnelRequestService> tunnelRequestServiceProvider;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new Configurator which uses the given tunnel request
|
|
||||||
* service provider to retrieve the necessary service to handle new
|
|
||||||
* connections requests.
|
|
||||||
*
|
|
||||||
* @param tunnelRequestServiceProvider
|
|
||||||
* The tunnel request service provider to use for all new
|
|
||||||
* connections.
|
|
||||||
*/
|
|
||||||
public Configurator(Provider<TunnelRequestService> tunnelRequestServiceProvider) {
|
|
||||||
this.tunnelRequestServiceProvider = tunnelRequestServiceProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void modifyHandshake(ServerEndpointConfig config,
|
|
||||||
HandshakeRequest request, HandshakeResponse response) {
|
|
||||||
|
|
||||||
super.modifyHandshake(config, request, response);
|
|
||||||
|
|
||||||
// Store tunnel request and tunnel request service for retrieval
|
|
||||||
// upon WebSocket open
|
|
||||||
Map<String, Object> userProperties = config.getUserProperties();
|
|
||||||
userProperties.clear();
|
|
||||||
userProperties.put(TUNNEL_REQUEST_PROPERTY, new WebSocketTunnelRequest(request));
|
|
||||||
userProperties.put(TUNNEL_REQUEST_SERVICE_PROPERTY, tunnelRequestServiceProvider.get());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected GuacamoleTunnel createTunnel(Session session,
|
|
||||||
EndpointConfig config) throws GuacamoleException {
|
|
||||||
|
|
||||||
Map<String, Object> userProperties = config.getUserProperties();
|
|
||||||
|
|
||||||
// Get original tunnel request
|
|
||||||
TunnelRequest tunnelRequest = (TunnelRequest) userProperties.get(TUNNEL_REQUEST_PROPERTY);
|
|
||||||
if (tunnelRequest == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
// Get tunnel request service
|
|
||||||
TunnelRequestService tunnelRequestService = (TunnelRequestService) userProperties.get(TUNNEL_REQUEST_SERVICE_PROPERTY);
|
|
||||||
if (tunnelRequestService == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
// Create and return tunnel
|
|
||||||
return tunnelRequestService.createTunnel(tunnelRequest);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,106 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file has been modified from the original, upstream version to facilitate
|
|
||||||
* integration with OpenUDS.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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.tunnel.websocket;
|
|
||||||
|
|
||||||
import com.google.inject.Provider;
|
|
||||||
import com.google.inject.servlet.ServletModule;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import javax.websocket.DeploymentException;
|
|
||||||
import javax.websocket.server.ServerContainer;
|
|
||||||
import javax.websocket.server.ServerEndpointConfig;
|
|
||||||
import org.apache.guacamole.tunnel.TunnelLoader;
|
|
||||||
import org.apache.guacamole.tunnel.TunnelRequestService;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads the JSR-356 WebSocket tunnel implementation.
|
|
||||||
*
|
|
||||||
* @author Michael Jumper
|
|
||||||
*/
|
|
||||||
public class WebSocketTunnelModule extends ServletModule implements TunnelLoader {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logger for this class.
|
|
||||||
*/
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(WebSocketTunnelModule.class);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSupported() {
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
// Attempt to find WebSocket servlet
|
|
||||||
Class.forName("javax.websocket.Endpoint");
|
|
||||||
|
|
||||||
// Support found
|
|
||||||
return true;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no such servlet class, this particular WebSocket support
|
|
||||||
// is not present
|
|
||||||
catch (ClassNotFoundException e) {}
|
|
||||||
catch (NoClassDefFoundError e) {}
|
|
||||||
|
|
||||||
// Support not found
|
|
||||||
return false;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void configureServlets() {
|
|
||||||
|
|
||||||
logger.info("Loading JSR-356 WebSocket support...");
|
|
||||||
|
|
||||||
// Get container
|
|
||||||
ServerContainer container = (ServerContainer) getServletContext().getAttribute("javax.websocket.server.ServerContainer");
|
|
||||||
if (container == null) {
|
|
||||||
logger.warn("ServerContainer attribute required by JSR-356 is missing. Cannot load JSR-356 WebSocket support.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Provider<TunnelRequestService> tunnelRequestServiceProvider = getProvider(TunnelRequestService.class);
|
|
||||||
|
|
||||||
// Build configuration for WebSocket tunnel
|
|
||||||
ServerEndpointConfig config =
|
|
||||||
ServerEndpointConfig.Builder.create(RestrictedGuacamoleWebSocketTunnelEndpoint.class, "/websocket-tunnel")
|
|
||||||
.configurator(new RestrictedGuacamoleWebSocketTunnelEndpoint.Configurator(tunnelRequestServiceProvider))
|
|
||||||
.subprotocols(Arrays.asList(new String[]{"guacamole"}))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
// Add configuration to container
|
|
||||||
container.addEndpoint(config);
|
|
||||||
|
|
||||||
}
|
|
||||||
catch (DeploymentException e) {
|
|
||||||
logger.error("Unable to deploy WebSocket tunnel.", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,67 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.tunnel.websocket;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import javax.websocket.server.HandshakeRequest;
|
|
||||||
import org.apache.guacamole.tunnel.TunnelRequest;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* WebSocket-specific implementation of TunnelRequest.
|
|
||||||
*
|
|
||||||
* @author Michael Jumper
|
|
||||||
*/
|
|
||||||
public class WebSocketTunnelRequest extends TunnelRequest {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All parameters passed via HTTP to the WebSocket handshake.
|
|
||||||
*/
|
|
||||||
private final Map<String, List<String>> handshakeParameters;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a TunnelRequest implementation which delegates parameter and
|
|
||||||
* session retrieval to the given HandshakeRequest.
|
|
||||||
*
|
|
||||||
* @param request The HandshakeRequest to wrap.
|
|
||||||
*/
|
|
||||||
public WebSocketTunnelRequest(HandshakeRequest request) {
|
|
||||||
this.handshakeParameters = request.getParameterMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getParameter(String name) {
|
|
||||||
|
|
||||||
// Pull list of values, if present
|
|
||||||
List<String> values = getParameterValues(name);
|
|
||||||
if (values == null || values.isEmpty())
|
|
||||||
return null;
|
|
||||||
|
|
||||||
// Return first parameter value arbitrarily
|
|
||||||
return values.get(0);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<String> getParameterValues(String name) {
|
|
||||||
return handshakeParameters.get(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,236 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.tunnel.websocket.jetty8;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
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.eclipse.jetty.websocket.WebSocket;
|
|
||||||
import org.eclipse.jetty.websocket.WebSocket.Connection;
|
|
||||||
import org.eclipse.jetty.websocket.WebSocketServlet;
|
|
||||||
import org.apache.guacamole.GuacamoleClientException;
|
|
||||||
import org.apache.guacamole.GuacamoleConnectionClosedException;
|
|
||||||
import org.apache.guacamole.protocol.GuacamoleInstruction;
|
|
||||||
import org.apache.guacamole.tunnel.http.HTTPTunnelRequest;
|
|
||||||
import org.apache.guacamole.tunnel.TunnelRequest;
|
|
||||||
import org.apache.guacamole.protocol.GuacamoleStatus;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A WebSocketServlet partial re-implementation of GuacamoleTunnelServlet.
|
|
||||||
*
|
|
||||||
* @author Michael Jumper
|
|
||||||
*/
|
|
||||||
public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logger for this class.
|
|
||||||
*/
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(GuacamoleWebSocketTunnelServlet.class);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The default, minimum buffer size for instructions.
|
|
||||||
*/
|
|
||||||
private static final int BUFFER_SIZE = 8192;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends the given status on the given WebSocket connection and closes the
|
|
||||||
* connection.
|
|
||||||
*
|
|
||||||
* @param connection The WebSocket connection to close.
|
|
||||||
* @param guac_status The status to send.
|
|
||||||
*/
|
|
||||||
public static void closeConnection(Connection connection,
|
|
||||||
GuacamoleStatus guac_status) {
|
|
||||||
|
|
||||||
connection.close(guac_status.getWebSocketCode(),
|
|
||||||
Integer.toString(guac_status.getGuacamoleStatusCode()));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) {
|
|
||||||
|
|
||||||
final TunnelRequest tunnelRequest = new HTTPTunnelRequest(request);
|
|
||||||
|
|
||||||
// Return new WebSocket which communicates through tunnel
|
|
||||||
return new WebSocket.OnTextMessage() {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The GuacamoleTunnel associated with the connected WebSocket. If
|
|
||||||
* the WebSocket has not yet been connected, this will be null.
|
|
||||||
*/
|
|
||||||
private GuacamoleTunnel tunnel = null;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onMessage(String string) {
|
|
||||||
|
|
||||||
// Ignore inbound messages if there is no associated tunnel
|
|
||||||
if (tunnel == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
GuacamoleWriter writer = tunnel.acquireWriter();
|
|
||||||
|
|
||||||
// Write message received
|
|
||||||
try {
|
|
||||||
writer.write(string.toCharArray());
|
|
||||||
}
|
|
||||||
catch (GuacamoleConnectionClosedException e) {
|
|
||||||
logger.debug("Connection to guacd closed.", e);
|
|
||||||
}
|
|
||||||
catch (GuacamoleException e) {
|
|
||||||
logger.debug("WebSocket tunnel write failed.", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
tunnel.releaseWriter();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onOpen(final Connection connection) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
tunnel = doConnect(tunnelRequest);
|
|
||||||
}
|
|
||||||
catch (GuacamoleException e) {
|
|
||||||
logger.error("Creation of WebSocket tunnel to guacd failed: {}", e.getMessage());
|
|
||||||
logger.debug("Error connecting WebSocket tunnel.", e);
|
|
||||||
closeConnection(connection, e.getStatus());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do not start connection if tunnel does not exist
|
|
||||||
if (tunnel == null) {
|
|
||||||
closeConnection(connection, GuacamoleStatus.RESOURCE_NOT_FOUND);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Thread readThread = new Thread() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
|
|
||||||
StringBuilder buffer = new StringBuilder(BUFFER_SIZE);
|
|
||||||
GuacamoleReader reader = tunnel.acquireReader();
|
|
||||||
char[] readMessage;
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
// Send tunnel UUID
|
|
||||||
connection.sendMessage(new GuacamoleInstruction(
|
|
||||||
GuacamoleTunnel.INTERNAL_DATA_OPCODE,
|
|
||||||
tunnel.getUUID().toString()
|
|
||||||
).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) {
|
|
||||||
connection.sendMessage(buffer.toString());
|
|
||||||
buffer.setLength(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// No more data
|
|
||||||
closeConnection(connection, 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(connection, e.getStatus());
|
|
||||||
}
|
|
||||||
catch (GuacamoleConnectionClosedException e) {
|
|
||||||
logger.debug("Connection to guacd closed.", e);
|
|
||||||
closeConnection(connection, GuacamoleStatus.SUCCESS);
|
|
||||||
}
|
|
||||||
catch (GuacamoleException e) {
|
|
||||||
logger.error("Connection to guacd terminated abnormally: {}", e.getMessage());
|
|
||||||
logger.debug("Internal error during connection to guacd.", e);
|
|
||||||
closeConnection(connection, e.getStatus());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
catch (IOException e) {
|
|
||||||
logger.debug("WebSocket tunnel read failed due to I/O error.", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
readThread.start();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClose(int i, String string) {
|
|
||||||
try {
|
|
||||||
if (tunnel != null)
|
|
||||||
tunnel.close();
|
|
||||||
}
|
|
||||||
catch (GuacamoleException e) {
|
|
||||||
logger.debug("Unable to close connection to guacd.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called whenever the JavaScript Guacamole client makes a connection
|
|
||||||
* request. 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 TunnelRequest 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(TunnelRequest request)
|
|
||||||
throws GuacamoleException;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file has been modified from the original, upstream version to facilitate
|
|
||||||
* integration with OpenUDS.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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.tunnel.websocket.jetty8;
|
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
|
||||||
import com.google.inject.Singleton;
|
|
||||||
import org.apache.guacamole.GuacamoleException;
|
|
||||||
import org.apache.guacamole.net.GuacamoleTunnel;
|
|
||||||
import org.apache.guacamole.tunnel.TunnelRequest;
|
|
||||||
import org.apache.guacamole.tunnel.TunnelRequestService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tunnel servlet implementation which uses WebSocket as a tunnel backend,
|
|
||||||
* rather than HTTP, properly parsing connection IDs included in the connection
|
|
||||||
* request.
|
|
||||||
*/
|
|
||||||
@Singleton
|
|
||||||
public class RestrictedGuacamoleWebSocketTunnelServlet extends GuacamoleWebSocketTunnelServlet {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Service for handling tunnel requests.
|
|
||||||
*/
|
|
||||||
@Inject
|
|
||||||
private TunnelRequestService tunnelRequestService;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected GuacamoleTunnel doConnect(TunnelRequest request)
|
|
||||||
throws GuacamoleException {
|
|
||||||
return tunnelRequestService.createTunnel(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,70 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.tunnel.websocket.jetty8;
|
|
||||||
|
|
||||||
import com.google.inject.servlet.ServletModule;
|
|
||||||
import org.apache.guacamole.tunnel.TunnelLoader;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads the Jetty 8 WebSocket tunnel implementation.
|
|
||||||
*
|
|
||||||
* @author Michael Jumper
|
|
||||||
*/
|
|
||||||
public class WebSocketTunnelModule extends ServletModule implements TunnelLoader {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logger for this class.
|
|
||||||
*/
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(WebSocketTunnelModule.class);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSupported() {
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
// Attempt to find WebSocket servlet
|
|
||||||
Class.forName("org.apache.guacamole.tunnel.websocket.jetty8.RestrictedGuacamoleWebSocketTunnelServlet");
|
|
||||||
|
|
||||||
// Support found
|
|
||||||
return true;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no such servlet class, this particular WebSocket support
|
|
||||||
// is not present
|
|
||||||
catch (ClassNotFoundException e) {}
|
|
||||||
catch (NoClassDefFoundError e) {}
|
|
||||||
|
|
||||||
// Support not found
|
|
||||||
return false;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void configureServlets() {
|
|
||||||
|
|
||||||
logger.info("Loading Jetty 8 WebSocket support...");
|
|
||||||
serve("/websocket-tunnel").with(RestrictedGuacamoleWebSocketTunnelServlet.class);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Jetty 8 WebSocket tunnel implementation. The classes here require Jetty 8.
|
|
||||||
*/
|
|
||||||
package org.apache.guacamole.tunnel.websocket.jetty8;
|
|
@ -1,247 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.tunnel.websocket.jetty9;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import org.eclipse.jetty.websocket.api.CloseStatus;
|
|
||||||
import org.eclipse.jetty.websocket.api.RemoteEndpoint;
|
|
||||||
import org.eclipse.jetty.websocket.api.Session;
|
|
||||||
import org.eclipse.jetty.websocket.api.WebSocketListener;
|
|
||||||
import org.apache.guacamole.GuacamoleClientException;
|
|
||||||
import org.apache.guacamole.GuacamoleConnectionClosedException;
|
|
||||||
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.protocol.GuacamoleInstruction;
|
|
||||||
import org.apache.guacamole.protocol.GuacamoleStatus;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* WebSocket listener implementation which provides a Guacamole tunnel
|
|
||||||
*
|
|
||||||
* @author Michael Jumper
|
|
||||||
*/
|
|
||||||
public abstract class GuacamoleWebSocketTunnelListener implements WebSocketListener {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The default, minimum buffer size for instructions.
|
|
||||||
*/
|
|
||||||
private static final int BUFFER_SIZE = 8192;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logger for this class.
|
|
||||||
*/
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(RestrictedGuacamoleWebSocketTunnelServlet.class);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The underlying GuacamoleTunnel. WebSocket reads/writes will be handled
|
|
||||||
* as reads/writes to this tunnel.
|
|
||||||
*/
|
|
||||||
private GuacamoleTunnel tunnel;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends the given status on the given WebSocket connection and closes the
|
|
||||||
* connection.
|
|
||||||
*
|
|
||||||
* @param session The outbound WebSocket connection to close.
|
|
||||||
* @param guac_status The status to send.
|
|
||||||
*/
|
|
||||||
private void closeConnection(Session session, GuacamoleStatus guac_status) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
int code = guac_status.getWebSocketCode();
|
|
||||||
String message = Integer.toString(guac_status.getGuacamoleStatusCode());
|
|
||||||
session.close(new CloseStatus(code, message));
|
|
||||||
}
|
|
||||||
catch (IOException e) {
|
|
||||||
logger.debug("Unable to close WebSocket connection.", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
* @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)
|
|
||||||
throws GuacamoleException;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onWebSocketConnect(final Session session) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
// Get tunnel
|
|
||||||
tunnel = createTunnel(session);
|
|
||||||
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());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare read transfer thread
|
|
||||||
Thread readThread = new Thread() {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remote (client) side of this connection
|
|
||||||
*/
|
|
||||||
private final RemoteEndpoint remote = session.getRemote();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
|
|
||||||
StringBuilder buffer = new StringBuilder(BUFFER_SIZE);
|
|
||||||
GuacamoleReader reader = tunnel.acquireReader();
|
|
||||||
char[] readMessage;
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
// Send tunnel UUID
|
|
||||||
remote.sendString(new GuacamoleInstruction(
|
|
||||||
GuacamoleTunnel.INTERNAL_DATA_OPCODE,
|
|
||||||
tunnel.getUUID().toString()
|
|
||||||
).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) {
|
|
||||||
remote.sendString(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());
|
|
||||||
}
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
catch (IOException e) {
|
|
||||||
logger.debug("I/O error prevents further reads.", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
readThread.start();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onWebSocketText(String message) {
|
|
||||||
|
|
||||||
// Ignore inbound messages if there is no associated tunnel
|
|
||||||
if (tunnel == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
GuacamoleWriter writer = tunnel.acquireWriter();
|
|
||||||
|
|
||||||
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
|
|
||||||
public void onWebSocketBinary(byte[] payload, int offset, int length) {
|
|
||||||
throw new UnsupportedOperationException("Binary WebSocket messages are not supported.");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onWebSocketError(Throwable t) {
|
|
||||||
|
|
||||||
logger.debug("WebSocket tunnel closing due to error.", t);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (tunnel != null)
|
|
||||||
tunnel.close();
|
|
||||||
}
|
|
||||||
catch (GuacamoleException e) {
|
|
||||||
logger.debug("Unable to close connection to guacd.", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onWebSocketClose(int statusCode, String reason) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (tunnel != null)
|
|
||||||
tunnel.close();
|
|
||||||
}
|
|
||||||
catch (GuacamoleException e) {
|
|
||||||
logger.debug("Unable to close connection to guacd.", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,74 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file has been modified from the original, upstream version to facilitate
|
|
||||||
* integration with OpenUDS.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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.tunnel.websocket.jetty9;
|
|
||||||
|
|
||||||
import org.eclipse.jetty.websocket.api.UpgradeRequest;
|
|
||||||
import org.eclipse.jetty.websocket.api.UpgradeResponse;
|
|
||||||
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
|
|
||||||
import org.apache.guacamole.tunnel.TunnelRequestService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* WebSocketCreator which selects the appropriate WebSocketListener
|
|
||||||
* implementation if the "guacamole" subprotocol is in use.
|
|
||||||
*
|
|
||||||
* @author Michael Jumper
|
|
||||||
*/
|
|
||||||
public class RestrictedGuacamoleWebSocketCreator implements WebSocketCreator {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Service for handling tunnel requests.
|
|
||||||
*/
|
|
||||||
private final TunnelRequestService tunnelRequestService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new WebSocketCreator which uses the given TunnelRequestService
|
|
||||||
* to create new GuacamoleTunnels for inbound requests.
|
|
||||||
*
|
|
||||||
* @param tunnelRequestService The service to use for inbound tunnel
|
|
||||||
* requests.
|
|
||||||
*/
|
|
||||||
public RestrictedGuacamoleWebSocketCreator(TunnelRequestService tunnelRequestService) {
|
|
||||||
this.tunnelRequestService = tunnelRequestService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object createWebSocket(UpgradeRequest request, UpgradeResponse response) {
|
|
||||||
|
|
||||||
// Validate and use "guacamole" subprotocol
|
|
||||||
for (String subprotocol : request.getSubProtocols()) {
|
|
||||||
|
|
||||||
if ("guacamole".equals(subprotocol)) {
|
|
||||||
response.setAcceptedSubProtocol(subprotocol);
|
|
||||||
return new RestrictedGuacamoleWebSocketTunnelListener(tunnelRequestService);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invalid protocol
|
|
||||||
return null;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,61 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file has been modified from the original, upstream version to facilitate
|
|
||||||
* integration with OpenUDS.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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.tunnel.websocket.jetty9;
|
|
||||||
|
|
||||||
import org.eclipse.jetty.websocket.api.Session;
|
|
||||||
import org.apache.guacamole.GuacamoleException;
|
|
||||||
import org.apache.guacamole.net.GuacamoleTunnel;
|
|
||||||
import org.apache.guacamole.tunnel.TunnelRequestService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* WebSocket listener implementation which properly parses connection IDs
|
|
||||||
* included in the connection request.
|
|
||||||
*
|
|
||||||
* @author Michael Jumper
|
|
||||||
*/
|
|
||||||
public class RestrictedGuacamoleWebSocketTunnelListener extends GuacamoleWebSocketTunnelListener {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Service for handling tunnel requests.
|
|
||||||
*/
|
|
||||||
private final TunnelRequestService tunnelRequestService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new WebSocketListener which uses the given TunnelRequestService
|
|
||||||
* to create new GuacamoleTunnels for inbound requests.
|
|
||||||
*
|
|
||||||
* @param tunnelRequestService The service to use for inbound tunnel
|
|
||||||
* requests.
|
|
||||||
*/
|
|
||||||
public RestrictedGuacamoleWebSocketTunnelListener(TunnelRequestService tunnelRequestService) {
|
|
||||||
this.tunnelRequestService = tunnelRequestService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected GuacamoleTunnel createTunnel(Session session) throws GuacamoleException {
|
|
||||||
return tunnelRequestService.createTunnel(new WebSocketTunnelRequest(session.getUpgradeRequest()));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,56 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file has been modified from the original, upstream version to facilitate
|
|
||||||
* integration with OpenUDS.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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.tunnel.websocket.jetty9;
|
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
|
||||||
import com.google.inject.Singleton;
|
|
||||||
import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
|
|
||||||
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
|
|
||||||
import org.apache.guacamole.tunnel.TunnelRequestService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A WebSocketServlet partial re-implementation of GuacamoleTunnelServlet.
|
|
||||||
*
|
|
||||||
* @author Michael Jumper
|
|
||||||
*/
|
|
||||||
@Singleton
|
|
||||||
public class RestrictedGuacamoleWebSocketTunnelServlet extends WebSocketServlet {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Service for handling tunnel requests.
|
|
||||||
*/
|
|
||||||
@Inject
|
|
||||||
private TunnelRequestService tunnelRequestService;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void configure(WebSocketServletFactory factory) {
|
|
||||||
|
|
||||||
// Register WebSocket implementation
|
|
||||||
factory.setCreator(new RestrictedGuacamoleWebSocketCreator(tunnelRequestService));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.tunnel.websocket.jetty9;
|
|
||||||
|
|
||||||
import com.google.inject.servlet.ServletModule;
|
|
||||||
import org.apache.guacamole.tunnel.TunnelLoader;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads the Jetty 9 WebSocket tunnel implementation.
|
|
||||||
*
|
|
||||||
* @author Michael Jumper
|
|
||||||
*/
|
|
||||||
public class WebSocketTunnelModule extends ServletModule implements TunnelLoader {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logger for this class.
|
|
||||||
*/
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(WebSocketTunnelModule.class);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSupported() {
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
// Attempt to find WebSocket servlet
|
|
||||||
Class.forName("org.apache.guacamole.tunnel.websocket.jetty9.RestrictedGuacamoleWebSocketTunnelServlet");
|
|
||||||
|
|
||||||
// Support found
|
|
||||||
return true;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no such servlet class, this particular WebSocket support
|
|
||||||
// is not present
|
|
||||||
catch (ClassNotFoundException e) {}
|
|
||||||
catch (NoClassDefFoundError e) {}
|
|
||||||
|
|
||||||
// Support not found
|
|
||||||
return false;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void configureServlets() {
|
|
||||||
|
|
||||||
logger.info("Loading Jetty 9 WebSocket support...");
|
|
||||||
serve("/websocket-tunnel").with(RestrictedGuacamoleWebSocketTunnelServlet.class);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,73 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.tunnel.websocket.jetty9;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import org.eclipse.jetty.websocket.api.UpgradeRequest;
|
|
||||||
import org.apache.guacamole.tunnel.TunnelRequest;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Jetty 9 WebSocket-specific implementation of TunnelRequest.
|
|
||||||
*
|
|
||||||
* @author Michael Jumper
|
|
||||||
*/
|
|
||||||
public class WebSocketTunnelRequest extends TunnelRequest {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All parameters passed via HTTP to the WebSocket handshake.
|
|
||||||
*/
|
|
||||||
private final Map<String, String[]> handshakeParameters;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a TunnelRequest implementation which delegates parameter and
|
|
||||||
* session retrieval to the given UpgradeRequest.
|
|
||||||
*
|
|
||||||
* @param request The UpgradeRequest to wrap.
|
|
||||||
*/
|
|
||||||
public WebSocketTunnelRequest(UpgradeRequest request) {
|
|
||||||
this.handshakeParameters = request.getParameterMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getParameter(String name) {
|
|
||||||
|
|
||||||
// Pull list of values, if present
|
|
||||||
List<String> values = getParameterValues(name);
|
|
||||||
if (values == null || values.isEmpty())
|
|
||||||
return null;
|
|
||||||
|
|
||||||
// Return first parameter value arbitrarily
|
|
||||||
return values.get(0);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<String> getParameterValues(String name) {
|
|
||||||
|
|
||||||
String[] values = handshakeParameters.get(name);
|
|
||||||
if (values == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return Arrays.asList(values);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Jetty 9 WebSocket tunnel implementation. The classes here require at least
|
|
||||||
* Jetty 9, prior to Jetty 9.1 (when support for JSR 356 was implemented).
|
|
||||||
*/
|
|
||||||
package org.apache.guacamole.tunnel.websocket.jetty9;
|
|
@ -1,24 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Standard WebSocket tunnel implementation. The classes here require a recent
|
|
||||||
* servlet container that supports JSR 356.
|
|
||||||
*/
|
|
||||||
package org.apache.guacamole.tunnel.websocket;
|
|
@ -1,269 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.tunnel.websocket.tomcat;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.Reader;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.CharBuffer;
|
|
||||||
import java.util.List;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
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.catalina.websocket.StreamInbound;
|
|
||||||
import org.apache.catalina.websocket.WebSocketServlet;
|
|
||||||
import org.apache.catalina.websocket.WsOutbound;
|
|
||||||
import org.apache.guacamole.GuacamoleClientException;
|
|
||||||
import org.apache.guacamole.GuacamoleConnectionClosedException;
|
|
||||||
import org.apache.guacamole.protocol.GuacamoleInstruction;
|
|
||||||
import org.apache.guacamole.tunnel.http.HTTPTunnelRequest;
|
|
||||||
import org.apache.guacamole.tunnel.TunnelRequest;
|
|
||||||
import org.apache.guacamole.protocol.GuacamoleStatus;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A WebSocketServlet partial re-implementation of GuacamoleTunnelServlet.
|
|
||||||
*
|
|
||||||
* @author Michael Jumper
|
|
||||||
*/
|
|
||||||
public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The default, minimum buffer size for instructions.
|
|
||||||
*/
|
|
||||||
private static final int BUFFER_SIZE = 8192;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logger for this class.
|
|
||||||
*/
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(GuacamoleWebSocketTunnelServlet.class);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends the given status on the given WebSocket connection and closes the
|
|
||||||
* connection.
|
|
||||||
*
|
|
||||||
* @param outbound The outbound WebSocket connection to close.
|
|
||||||
* @param guac_status The status to send.
|
|
||||||
*/
|
|
||||||
public void closeConnection(WsOutbound outbound, GuacamoleStatus guac_status) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
byte[] message = Integer.toString(guac_status.getGuacamoleStatusCode()).getBytes("UTF-8");
|
|
||||||
outbound.close(guac_status.getWebSocketCode(), ByteBuffer.wrap(message));
|
|
||||||
}
|
|
||||||
catch (IOException e) {
|
|
||||||
logger.debug("Unable to close WebSocket tunnel.", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String selectSubProtocol(List<String> subProtocols) {
|
|
||||||
|
|
||||||
// Search for expected protocol
|
|
||||||
for (String protocol : subProtocols)
|
|
||||||
if ("guacamole".equals(protocol))
|
|
||||||
return "guacamole";
|
|
||||||
|
|
||||||
// Otherwise, fail
|
|
||||||
return null;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public StreamInbound createWebSocketInbound(String protocol,
|
|
||||||
HttpServletRequest request) {
|
|
||||||
|
|
||||||
final TunnelRequest tunnelRequest = new HTTPTunnelRequest(request);
|
|
||||||
|
|
||||||
// Return new WebSocket which communicates through tunnel
|
|
||||||
return new StreamInbound() {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The GuacamoleTunnel associated with the connected WebSocket. If
|
|
||||||
* the WebSocket has not yet been connected, this will be null.
|
|
||||||
*/
|
|
||||||
private GuacamoleTunnel tunnel = null;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onTextData(Reader reader) throws IOException {
|
|
||||||
|
|
||||||
// Ignore inbound messages if there is no associated tunnel
|
|
||||||
if (tunnel == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
GuacamoleWriter writer = tunnel.acquireWriter();
|
|
||||||
|
|
||||||
// Write all available data
|
|
||||||
try {
|
|
||||||
|
|
||||||
char[] buffer = new char[BUFFER_SIZE];
|
|
||||||
|
|
||||||
int num_read;
|
|
||||||
while ((num_read = reader.read(buffer)) > 0)
|
|
||||||
writer.write(buffer, 0, num_read);
|
|
||||||
|
|
||||||
}
|
|
||||||
catch (GuacamoleConnectionClosedException e) {
|
|
||||||
logger.debug("Connection to guacd closed.", e);
|
|
||||||
}
|
|
||||||
catch (GuacamoleException e) {
|
|
||||||
logger.debug("WebSocket tunnel write failed.", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
tunnel.releaseWriter();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onOpen(final WsOutbound outbound) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
tunnel = doConnect(tunnelRequest);
|
|
||||||
}
|
|
||||||
catch (GuacamoleException e) {
|
|
||||||
logger.error("Creation of WebSocket tunnel to guacd failed: {}", e.getMessage());
|
|
||||||
logger.debug("Error connecting WebSocket tunnel.", e);
|
|
||||||
closeConnection(outbound, e.getStatus());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do not start connection if tunnel does not exist
|
|
||||||
if (tunnel == null) {
|
|
||||||
closeConnection(outbound, GuacamoleStatus.RESOURCE_NOT_FOUND);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Thread readThread = new Thread() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
|
|
||||||
StringBuilder buffer = new StringBuilder(BUFFER_SIZE);
|
|
||||||
GuacamoleReader reader = tunnel.acquireReader();
|
|
||||||
char[] readMessage;
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
// Send tunnel UUID
|
|
||||||
outbound.writeTextMessage(CharBuffer.wrap(new GuacamoleInstruction(
|
|
||||||
GuacamoleTunnel.INTERNAL_DATA_OPCODE,
|
|
||||||
tunnel.getUUID().toString()
|
|
||||||
).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) {
|
|
||||||
outbound.writeTextMessage(CharBuffer.wrap(buffer));
|
|
||||||
buffer.setLength(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// No more data
|
|
||||||
closeConnection(outbound, 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(outbound, e.getStatus());
|
|
||||||
}
|
|
||||||
catch (GuacamoleConnectionClosedException e) {
|
|
||||||
logger.debug("Connection to guacd closed.", e);
|
|
||||||
closeConnection(outbound, GuacamoleStatus.SUCCESS);
|
|
||||||
}
|
|
||||||
catch (GuacamoleException e) {
|
|
||||||
logger.error("Connection to guacd terminated abnormally: {}", e.getMessage());
|
|
||||||
logger.debug("Internal error during connection to guacd.", e);
|
|
||||||
closeConnection(outbound, e.getStatus());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
catch (IOException e) {
|
|
||||||
logger.debug("I/O error prevents further reads.", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
readThread.start();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClose(int i) {
|
|
||||||
try {
|
|
||||||
if (tunnel != null)
|
|
||||||
tunnel.close();
|
|
||||||
}
|
|
||||||
catch (GuacamoleException e) {
|
|
||||||
logger.debug("Unable to close connection to guacd.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onBinaryData(InputStream in) throws IOException {
|
|
||||||
throw new UnsupportedOperationException("Not supported yet.");
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called whenever the JavaScript Guacamole client makes a connection
|
|
||||||
* request. 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 TunnelRequest 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(TunnelRequest request)
|
|
||||||
throws GuacamoleException;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file has been modified from the original, upstream version to facilitate
|
|
||||||
* integration with OpenUDS.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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.tunnel.websocket.tomcat;
|
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
|
||||||
import com.google.inject.Singleton;
|
|
||||||
import org.apache.guacamole.GuacamoleException;
|
|
||||||
import org.apache.guacamole.net.GuacamoleTunnel;
|
|
||||||
import org.apache.guacamole.tunnel.TunnelRequest;
|
|
||||||
import org.apache.guacamole.tunnel.TunnelRequestService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tunnel servlet implementation which uses WebSocket as a tunnel backend,
|
|
||||||
* rather than HTTP, properly parsing connection IDs included in the connection
|
|
||||||
* request.
|
|
||||||
*/
|
|
||||||
@Singleton
|
|
||||||
public class RestrictedGuacamoleWebSocketTunnelServlet extends GuacamoleWebSocketTunnelServlet {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Service for handling tunnel requests.
|
|
||||||
*/
|
|
||||||
@Inject
|
|
||||||
private TunnelRequestService tunnelRequestService;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected GuacamoleTunnel doConnect(TunnelRequest request)
|
|
||||||
throws GuacamoleException {
|
|
||||||
return tunnelRequestService.createTunnel(request);
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
@ -1,70 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.tunnel.websocket.tomcat;
|
|
||||||
|
|
||||||
import com.google.inject.servlet.ServletModule;
|
|
||||||
import org.apache.guacamole.tunnel.TunnelLoader;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads the Jetty 9 WebSocket tunnel implementation.
|
|
||||||
*
|
|
||||||
* @author Michael Jumper
|
|
||||||
*/
|
|
||||||
public class WebSocketTunnelModule extends ServletModule implements TunnelLoader {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logger for this class.
|
|
||||||
*/
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(WebSocketTunnelModule.class);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSupported() {
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
// Attempt to find WebSocket servlet
|
|
||||||
Class.forName("org.apache.guacamole.tunnel.websocket.tomcat.RestrictedGuacamoleWebSocketTunnelServlet");
|
|
||||||
|
|
||||||
// Support found
|
|
||||||
return true;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no such servlet class, this particular WebSocket support
|
|
||||||
// is not present
|
|
||||||
catch (ClassNotFoundException e) {}
|
|
||||||
catch (NoClassDefFoundError e) {}
|
|
||||||
|
|
||||||
// Support not found
|
|
||||||
return false;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void configureServlets() {
|
|
||||||
|
|
||||||
logger.info("Loading Tomcat 7 WebSocket support...");
|
|
||||||
serve("/websocket-tunnel").with(RestrictedGuacamoleWebSocketTunnelServlet.class);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tomcat WebSocket tunnel implementation. The classes here require at least
|
|
||||||
* Tomcat 7.0, and may change significantly as there is no common WebSocket
|
|
||||||
* API for Java yet.
|
|
||||||
*/
|
|
||||||
package org.apache.guacamole.tunnel.websocket.tomcat;
|
|
@ -1,54 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2015 Virtual Cable S.L.
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without modification,
|
|
||||||
* are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* * Redistributions of source code must retain the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer.
|
|
||||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
* * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
|
||||||
* may be used to endorse or promote products derived from this software
|
|
||||||
* without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.openuds.guacamole;
|
|
||||||
|
|
||||||
import com.google.inject.Guice;
|
|
||||||
import com.google.inject.Injector;
|
|
||||||
import com.google.inject.servlet.GuiceServletContextListener;
|
|
||||||
import org.apache.guacamole.tunnel.TunnelModule;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ServletContextListener implementation which initializes Guice and services
|
|
||||||
* specific to OpenUDS.
|
|
||||||
*/
|
|
||||||
public class UDSServletContextListener extends GuiceServletContextListener {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Injector getInjector() {
|
|
||||||
|
|
||||||
// Create an injector with OpenUDS- and Guacamole-specific services
|
|
||||||
// properly bound
|
|
||||||
return Guice.createInjector(
|
|
||||||
new UDSModule(),
|
|
||||||
new TunnelModule()
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
# We can specify the uds server here as:
|
|
||||||
# uds=http://172.27.0.1:8000
|
|
||||||
# Or we can get the server from an external file. The path part of the URL inside the file will be removed,
|
|
||||||
# so, if we put "https://example.com/other", the value of uds configuration will be "https://example.com"
|
|
||||||
udsfile=/etc/uds.conf
|
|
@ -1,55 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!--
|
|
||||||
Copyright (c) 2015 Virtual Cable S.L.
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification,
|
|
||||||
are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
* Neither the name of Virtual Cable S.L. nor the names of its contributors
|
|
||||||
may be used to endorse or promote products derived from this software
|
|
||||||
without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
-->
|
|
||||||
<web-app version="2.5"
|
|
||||||
xmlns="http://java.sun.com/xml/ns/javaee"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
|
|
||||||
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
|
|
||||||
|
|
||||||
<!-- Basic config -->
|
|
||||||
<welcome-file-list>
|
|
||||||
<welcome-file>index.xhtml</welcome-file>
|
|
||||||
</welcome-file-list>
|
|
||||||
|
|
||||||
<!-- Initialization ServletContextListener -->
|
|
||||||
<listener>
|
|
||||||
<listener-class>org.openuds.guacamole.UDSServletContextListener</listener-class>
|
|
||||||
</listener>
|
|
||||||
|
|
||||||
<!-- Guice -->
|
|
||||||
<filter>
|
|
||||||
<filter-name>guiceFilter</filter-name>
|
|
||||||
<filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
|
|
||||||
</filter>
|
|
||||||
<filter-mapping>
|
|
||||||
<filter-name>guiceFilter</filter-name>
|
|
||||||
<url-pattern>/*</url-pattern>
|
|
||||||
</filter-mapping>
|
|
||||||
|
|
||||||
</web-app>
|
|
@ -1,182 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE html>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Copyright (C) 2017 Glyptodon, Inc.
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<link rel="icon" type="image/png" href="images/udsicon.png"/>
|
|
||||||
<link rel="stylesheet" type="text/css" href="styles/ui.css"/>
|
|
||||||
<link rel="stylesheet" type="text/css" href="styles/client.css"/>
|
|
||||||
<link rel="stylesheet" type="text/css" href="styles/keyboard.css"/>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, target-densitydpi=medium-dpi"/>
|
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes"/>
|
|
||||||
<title>UDS Guacamole Access</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div id="main">
|
|
||||||
|
|
||||||
<!-- Display -->
|
|
||||||
<div class="displayOuter">
|
|
||||||
<div class="displayMiddle">
|
|
||||||
<div id="display">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Text input target -->
|
|
||||||
<div id="text-input"><div id="text-input-field"><div id="sent-history"></div><textarea rows="1" id="target"></textarea></div><div id="text-input-buttons"><button class="key" data-keysym="0xFFE3" data-sticky="true">Ctrl</button><button class="key" data-keysym="0xFFE9" data-sticky="true">Alt</button><button class="key" data-keysym="0xFF1B">Esc</button><button class="key" data-keysym="0xFF09">Tab</button></div></div>
|
|
||||||
|
|
||||||
<!-- Dimensional clone of viewport -->
|
|
||||||
<div id="viewportClone"/>
|
|
||||||
|
|
||||||
<!-- Notification area -->
|
|
||||||
<div id="notificationArea"/>
|
|
||||||
|
|
||||||
<!-- Menu -->
|
|
||||||
<div id="menu">
|
|
||||||
<h2 id="menu-title">Guacamole ${project.version}</h2>
|
|
||||||
|
|
||||||
<h3>Clipboard</h3>
|
|
||||||
<div class="content" id="clipboard-settings">
|
|
||||||
<p class="description">Text copied/cut within Guacamole will appear here. Changes to the text below will affect the remote clipboard.</p>
|
|
||||||
<textarea rows="10" cols="40" id="clipboard"></textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3>Input method</h3>
|
|
||||||
<div class="content" id="keyboard-settings">
|
|
||||||
|
|
||||||
<!-- No IME -->
|
|
||||||
<div class="choice">
|
|
||||||
<label><input name="input-method" type="radio" value="ime-none" checked="checked" id="ime-none"/> None</label>
|
|
||||||
<p class="caption"><label for="ime-none">No input method is used. Keyboard input is accepted from
|
|
||||||
a connected, physical keyboard.</label></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Text input -->
|
|
||||||
<div class="choice">
|
|
||||||
<div class="figure"><label for="ime-text"><img src="images/settings/tablet-keys.png" alt=""/></label></div>
|
|
||||||
<label><input name="input-method" type="radio" value="ime-text" id="ime-text"/> Text input</label>
|
|
||||||
<p class="caption"><label for="ime-text">
|
|
||||||
Allow typing of text, and emulate keyboard events based on the
|
|
||||||
typed text. This is necessary for devices such as mobile phones that lack a physical keyboard.</label></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Guac OSK -->
|
|
||||||
<div class="choice">
|
|
||||||
<label><input name="input-method" type="radio" value="ime-osk" id="ime-osk"/> On-screen keyboard</label>
|
|
||||||
<p class="caption"><label for="ime-osk">Display and accept input from the built-in Guacamole on-screen
|
|
||||||
keyboard. The on-screen keyboard allows typing of key combinations that may otherwise be impossible
|
|
||||||
(such as Ctrl-Alt-Del).</label></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3>Mouse emulation mode</h3>
|
|
||||||
<div class="content" id="mouse-settings">
|
|
||||||
<p class="description">Determines how the remote mouse behaves with respect to touches.</p>
|
|
||||||
|
|
||||||
<!-- Touchscreen -->
|
|
||||||
<div class="choice">
|
|
||||||
<input name="mouse-mode" type="radio" value="absolute" checked="checked" id="absolute"/>
|
|
||||||
<div class="figure">
|
|
||||||
<label for="absolute"><img src="images/settings/touchscreen.png" alt=""/></label>
|
|
||||||
<p class="caption"><label for="absolute">Tap to click. The click occurs at the location of the touch.</label></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Touchpad -->
|
|
||||||
<div class="choice">
|
|
||||||
<input name="mouse-mode" type="radio" value="relative" id="relative"/>
|
|
||||||
<div class="figure">
|
|
||||||
<label for="relative"><img src="images/settings/touchpad.png" alt=""/></label>
|
|
||||||
<p class="caption"><label for="relative">Drag to move the mouse pointer and tap to click. The click occurs at the location of the pointer.</label></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3>Display</h3>
|
|
||||||
<div class="content">
|
|
||||||
<div id="zoom-settings">
|
|
||||||
<div id="zoom-out"><img src="images/settings/zoom-out.png" alt="-"/></div>
|
|
||||||
<div id="zoom-state">100%</div>
|
|
||||||
<div id="zoom-in"><img src="images/settings/zoom-in.png" alt="+"/></div>
|
|
||||||
</div>
|
|
||||||
<div><label><input type="checkbox" id="auto-fit" checked="checked"/> Automatically fit to browser window</label></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Images which should be preloaded -->
|
|
||||||
<div id="preload">
|
|
||||||
<img src="images/action-icons/guac-close.png"/>
|
|
||||||
<img src="images/progress.png"/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script type="text/javascript" src="scripts/lib/blob/blob.js"></script>
|
|
||||||
<script type="text/javascript" src="scripts/lib/filesaver/filesaver.js"></script>
|
|
||||||
|
|
||||||
<!-- guacamole-common-js -->
|
|
||||||
<script type="text/javascript" src="guacamole-common-js/all.min.js"></script>
|
|
||||||
|
|
||||||
<!-- guacamole-default-webapp scripts -->
|
|
||||||
<script type="text/javascript" src="scripts/session.js"></script>
|
|
||||||
<script type="text/javascript" src="scripts/guac-ui.js"></script>
|
|
||||||
<script type="text/javascript" src="scripts/client-ui.js"></script>
|
|
||||||
|
|
||||||
<!-- Init -->
|
|
||||||
<script type="text/javascript"> /* <![CDATA[ */
|
|
||||||
|
|
||||||
// Adapted to 1.5
|
|
||||||
function getQueryParams(qs) {
|
|
||||||
qs = qs.split("+").join(" ");
|
|
||||||
|
|
||||||
var params = {}, re=/[^?]*\?([^&]*)&(.*)/;
|
|
||||||
|
|
||||||
var res = re.exec(qs);
|
|
||||||
|
|
||||||
params.data = res[1];
|
|
||||||
params.exit = res[2];
|
|
||||||
|
|
||||||
return params;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.query = getQueryParams(document.location.href);
|
|
||||||
|
|
||||||
// Start connect after control returns from onload (allow browser
|
|
||||||
// to consider the page loaded).
|
|
||||||
window.onload = function() {
|
|
||||||
window.setTimeout(GuacUI.Client.connect, 10);
|
|
||||||
};
|
|
||||||
|
|
||||||
/* ]]> */ </script>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
@ -1,312 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
|
|
||||||
<!DOCTYPE keyboard PUBLIC
|
|
||||||
"-//Guacamole/Guacamole Onscreen Keyboard DTD 0.6.0//EN"
|
|
||||||
"http://guac-dev.org/pub/dtd/guacamole-osk-0.6.0.dtd">
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Guacamole - Clientless Remote Desktop
|
|
||||||
Copyright (C) 2010 Michael Jumper
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU Affero General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<keyboard lang="en_US" layout="qwerty" size="16.3">
|
|
||||||
<row>
|
|
||||||
<key size="1.5">
|
|
||||||
<cap keysym="0xFF09">Tab</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>q</cap>
|
|
||||||
<cap if="numsym">1</cap>
|
|
||||||
<cap if="shift">Q</cap>
|
|
||||||
<cap if="numsym,shift">q</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>w</cap>
|
|
||||||
<cap if="numsym">2</cap>
|
|
||||||
<cap if="shift">W</cap>
|
|
||||||
<cap if="numsym,shift">w</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>e</cap>
|
|
||||||
<cap if="numsym">3</cap>
|
|
||||||
<cap if="shift">E</cap>
|
|
||||||
<cap if="numsym,shift">e</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>r</cap>
|
|
||||||
<cap if="numsym">4</cap>
|
|
||||||
<cap if="shift">R</cap>
|
|
||||||
<cap if="numsym,shift">r</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>t</cap>
|
|
||||||
<cap if="numsym">5</cap>
|
|
||||||
<cap if="shift">T</cap>
|
|
||||||
<cap if="numsym,shift">t</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>y</cap>
|
|
||||||
<cap if="numsym">6</cap>
|
|
||||||
<cap if="shift">Y</cap>
|
|
||||||
<cap if="numsym,shift">y</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>u</cap>
|
|
||||||
<cap if="numsym">7</cap>
|
|
||||||
<cap if="shift">U</cap>
|
|
||||||
<cap if="numsym,shift">u</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>i</cap>
|
|
||||||
<cap if="numsym">8</cap>
|
|
||||||
<cap if="shift">I</cap>
|
|
||||||
<cap if="numsym,shift">i</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>o</cap>
|
|
||||||
<cap if="numsym">9</cap>
|
|
||||||
<cap if="shift">O</cap>
|
|
||||||
<cap if="numsym,shift">o</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>p</cap>
|
|
||||||
<cap if="numsym">0</cap>
|
|
||||||
<cap if="shift">P</cap>
|
|
||||||
<cap if="numsym,shift">p</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>[</cap>
|
|
||||||
<cap if="shift">{</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>]</cap>
|
|
||||||
<cap if="shift">}</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key size="1.5">
|
|
||||||
<cap keysym="0xFF08">Back</cap>
|
|
||||||
</key>
|
|
||||||
</row>
|
|
||||||
|
|
||||||
<row><gap size="0.1"/></row>
|
|
||||||
|
|
||||||
<row>
|
|
||||||
<key size="1.85" class="numsym">
|
|
||||||
<cap modifier="numsym" sticky="true">?123</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>a</cap>
|
|
||||||
<cap if="numsym">#</cap>
|
|
||||||
<cap if="shift">A</cap>
|
|
||||||
<cap if="numsym,shift">a</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>s</cap>
|
|
||||||
<cap if="numsym">$</cap>
|
|
||||||
<cap if="shift">S</cap>
|
|
||||||
<cap if="numsym,shift">s</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>d</cap>
|
|
||||||
<cap if="numsym">%</cap>
|
|
||||||
<cap if="shift">D</cap>
|
|
||||||
<cap if="numsym,shift">d</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>f</cap>
|
|
||||||
<cap if="numsym">&</cap>
|
|
||||||
<cap if="shift">F</cap>
|
|
||||||
<cap if="numsym,shift">f</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>g</cap>
|
|
||||||
<cap if="numsym">*</cap>
|
|
||||||
<cap if="shift">G</cap>
|
|
||||||
<cap if="numsym,shift">g</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>h</cap>
|
|
||||||
<cap if="numsym">-</cap>
|
|
||||||
<cap if="shift">H</cap>
|
|
||||||
<cap if="numsym,shift">h</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>j</cap>
|
|
||||||
<cap if="numsym">+</cap>
|
|
||||||
<cap if="shift">J</cap>
|
|
||||||
<cap if="numsym,shift">j</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>k</cap>
|
|
||||||
<cap if="numsym">(</cap>
|
|
||||||
<cap if="shift">K</cap>
|
|
||||||
<cap if="numsym,shift">k</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>l</cap>
|
|
||||||
<cap if="numsym">)</cap>
|
|
||||||
<cap if="shift">L</cap>
|
|
||||||
<cap if="numsym,shift">l</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>;</cap>
|
|
||||||
<cap if="shift">:</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>'</cap>
|
|
||||||
<cap if="shift">"</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key size="2.25">
|
|
||||||
<cap keysym="0xFF0D">Enter</cap>
|
|
||||||
</key>
|
|
||||||
</row>
|
|
||||||
|
|
||||||
<row><gap size="0.1"/></row>
|
|
||||||
|
|
||||||
<row>
|
|
||||||
<key size="2.1" class="shift">
|
|
||||||
<cap modifier="shift" keysym="0xFFE1">Shift</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>z</cap>
|
|
||||||
<cap if="numsym"><</cap>
|
|
||||||
<cap if="shift">Z</cap>
|
|
||||||
<cap if="numsym,shift">z</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>x</cap>
|
|
||||||
<cap if="numsym">></cap>
|
|
||||||
<cap if="shift">X</cap>
|
|
||||||
<cap if="numsym,shift">x</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>c</cap>
|
|
||||||
<cap if="numsym">=</cap>
|
|
||||||
<cap if="shift">C</cap>
|
|
||||||
<cap if="numsym,shift">c</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>v</cap>
|
|
||||||
<cap if="numsym">'</cap>
|
|
||||||
<cap if="shift">V</cap>
|
|
||||||
<cap if="numsym,shift">v</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>b</cap>
|
|
||||||
<cap if="numsym">;</cap>
|
|
||||||
<cap if="shift">B</cap>
|
|
||||||
<cap if="numsym,shift">b</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>n</cap>
|
|
||||||
<cap if="numsym">,</cap>
|
|
||||||
<cap if="shift">N</cap>
|
|
||||||
<cap if="numsym,shift">n</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>m</cap>
|
|
||||||
<cap if="numsym">.</cap>
|
|
||||||
<cap if="shift">M</cap>
|
|
||||||
<cap if="numsym,shift">m</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>,</cap>
|
|
||||||
<cap if="numsym">!</cap>
|
|
||||||
<cap if="shift">!</cap>
|
|
||||||
<cap if="numsym,shift">!</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>.</cap>
|
|
||||||
<cap if="numsym">?</cap>
|
|
||||||
<cap if="shift">?</cap>
|
|
||||||
<cap if="numsym,shift">?</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>/</cap>
|
|
||||||
<cap if="shift">?</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key size="3.1" class="shift">
|
|
||||||
<cap modifier="shift" keysym="0xFFE2">Shift</cap>
|
|
||||||
</key>
|
|
||||||
</row>
|
|
||||||
|
|
||||||
<row><gap size="0.1"/></row>
|
|
||||||
|
|
||||||
<row>
|
|
||||||
<key size="1.6" class="control">
|
|
||||||
<cap modifier="control" keysym="0xFFE3">Ctrl</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key size="1.6" class="super">
|
|
||||||
<cap modifier="super" keysym="0xFFEB">Super</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key size="1.6" class="alt">
|
|
||||||
<cap modifier="alt" keysym="0xFFE9">Alt</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key size="6.1">
|
|
||||||
<cap> </cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key size="1.6" class="alt">
|
|
||||||
<cap modifier="alt" keysym="0xFFEA">Alt</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key size="1.6">
|
|
||||||
<cap keysym="0xFF67">Menu</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key size="1.6" class="control">
|
|
||||||
<cap modifier="control" keysym="0xFFE4">Ctrl</cap>
|
|
||||||
</key>
|
|
||||||
</row>
|
|
||||||
</keyboard>
|
|
@ -1,496 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
|
|
||||||
<!DOCTYPE keyboard PUBLIC
|
|
||||||
"-//Guacamole/Guacamole Onscreen Keyboard DTD 0.6.0//EN"
|
|
||||||
"http://guac-dev.org/pub/dtd/guacamole-osk-0.6.0.dtd">
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Guacamole - Clientless Remote Desktop
|
|
||||||
Copyright (C) 2010 Michael Jumper
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU Affero General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<keyboard lang="en_US" layout="qwerty" size="22">
|
|
||||||
<row>
|
|
||||||
<key>
|
|
||||||
<cap keysym="0xFF1B">Esc</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.8"/>
|
|
||||||
<key>
|
|
||||||
<cap keysym="0xFFBE">F1</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap keysym="0xFFBF">F2</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap keysym="0xFFC0">F3</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap keysym="0xFFC1">F4</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.8"/>
|
|
||||||
<key>
|
|
||||||
<cap keysym="0xFFC2">F5</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap keysym="0xFFC3">F6</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap keysym="0xFFC4">F7</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap keysym="0xFFC5">F8</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.8"/>
|
|
||||||
<key>
|
|
||||||
<cap keysym="0xFFC6">F9</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap keysym="0xFFC7">F10</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap keysym="0xFFC8">F11</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap keysym="0xFFC9">F12</cap>
|
|
||||||
</key>
|
|
||||||
</row>
|
|
||||||
<row>
|
|
||||||
<gap size="0.25"/>
|
|
||||||
</row>
|
|
||||||
<column>
|
|
||||||
<row>
|
|
||||||
<key>
|
|
||||||
<cap>`</cap>
|
|
||||||
<cap if="shift">~</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>1</cap>
|
|
||||||
<cap if="shift">!</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>2</cap>
|
|
||||||
<cap if="shift">@</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>3</cap>
|
|
||||||
<cap if="shift">#</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>4</cap>
|
|
||||||
<cap if="shift">$</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>5</cap>
|
|
||||||
<cap if="shift">%</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>6</cap>
|
|
||||||
<cap if="shift">^</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>7</cap>
|
|
||||||
<cap if="shift">&</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>8</cap>
|
|
||||||
<cap if="shift">*</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>9</cap>
|
|
||||||
<cap if="shift">(</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>0</cap>
|
|
||||||
<cap if="shift">)</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>-</cap>
|
|
||||||
<cap if="shift">_</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>=</cap>
|
|
||||||
<cap if="shift">+</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key size="2">
|
|
||||||
<cap keysym="0xFF08">Back</cap>
|
|
||||||
</key>
|
|
||||||
</row>
|
|
||||||
|
|
||||||
<row><gap size="0.1"/></row>
|
|
||||||
|
|
||||||
<row>
|
|
||||||
<key size="1.5">
|
|
||||||
<cap keysym="0xFF09">Tab</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>q</cap>
|
|
||||||
<cap if="caps">Q</cap>
|
|
||||||
<cap if="shift">Q</cap>
|
|
||||||
<cap if="caps,shift">q</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>w</cap>
|
|
||||||
<cap if="caps">W</cap>
|
|
||||||
<cap if="shift">W</cap>
|
|
||||||
<cap if="caps,shift">w</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>e</cap>
|
|
||||||
<cap if="caps">E</cap>
|
|
||||||
<cap if="shift">E</cap>
|
|
||||||
<cap if="caps,shift">e</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>r</cap>
|
|
||||||
<cap if="caps">R</cap>
|
|
||||||
<cap if="shift">R</cap>
|
|
||||||
<cap if="caps,shift">r</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>t</cap>
|
|
||||||
<cap if="caps">T</cap>
|
|
||||||
<cap if="shift">T</cap>
|
|
||||||
<cap if="caps,shift">t</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>y</cap>
|
|
||||||
<cap if="caps">Y</cap>
|
|
||||||
<cap if="shift">Y</cap>
|
|
||||||
<cap if="caps,shift">y</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>u</cap>
|
|
||||||
<cap if="caps">U</cap>
|
|
||||||
<cap if="shift">U</cap>
|
|
||||||
<cap if="caps,shift">u</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>i</cap>
|
|
||||||
<cap if="caps">I</cap>
|
|
||||||
<cap if="shift">I</cap>
|
|
||||||
<cap if="caps,shift">i</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>o</cap>
|
|
||||||
<cap if="caps">O</cap>
|
|
||||||
<cap if="shift">O</cap>
|
|
||||||
<cap if="caps,shift">o</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>p</cap>
|
|
||||||
<cap if="caps">P</cap>
|
|
||||||
<cap if="shift">P</cap>
|
|
||||||
<cap if="caps,shift">p</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>[</cap>
|
|
||||||
<cap if="shift">{</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>]</cap>
|
|
||||||
<cap if="shift">}</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key size="1.5">
|
|
||||||
<cap>\</cap>
|
|
||||||
<cap if="shift">|</cap>
|
|
||||||
</key>
|
|
||||||
</row>
|
|
||||||
|
|
||||||
<row><gap size="0.1"/></row>
|
|
||||||
|
|
||||||
<row>
|
|
||||||
<key size="1.85">
|
|
||||||
<cap modifier="caps" keysym="0xFFE5" sticky="true">Caps</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>a</cap>
|
|
||||||
<cap if="caps">A</cap>
|
|
||||||
<cap if="shift">A</cap>
|
|
||||||
<cap if="caps,shift">a</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>s</cap>
|
|
||||||
<cap if="caps">S</cap>
|
|
||||||
<cap if="shift">S</cap>
|
|
||||||
<cap if="caps,shift">s</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>d</cap>
|
|
||||||
<cap if="caps">D</cap>
|
|
||||||
<cap if="shift">D</cap>
|
|
||||||
<cap if="caps,shift">d</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>f</cap>
|
|
||||||
<cap if="caps">F</cap>
|
|
||||||
<cap if="shift">F</cap>
|
|
||||||
<cap if="caps,shift">f</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>g</cap>
|
|
||||||
<cap if="caps">G</cap>
|
|
||||||
<cap if="shift">G</cap>
|
|
||||||
<cap if="caps,shift">g</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>h</cap>
|
|
||||||
<cap if="caps">H</cap>
|
|
||||||
<cap if="shift">H</cap>
|
|
||||||
<cap if="caps,shift">h</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>j</cap>
|
|
||||||
<cap if="caps">J</cap>
|
|
||||||
<cap if="shift">J</cap>
|
|
||||||
<cap if="caps,shift">j</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>k</cap>
|
|
||||||
<cap if="caps">K</cap>
|
|
||||||
<cap if="shift">K</cap>
|
|
||||||
<cap if="caps,shift">k</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>l</cap>
|
|
||||||
<cap if="caps">L</cap>
|
|
||||||
<cap if="shift">L</cap>
|
|
||||||
<cap if="caps,shift">l</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>;</cap>
|
|
||||||
<cap if="shift">:</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>'</cap>
|
|
||||||
<cap if="shift">"</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key size="2.25">
|
|
||||||
<cap keysym="0xFF0D">Enter</cap>
|
|
||||||
</key>
|
|
||||||
</row>
|
|
||||||
|
|
||||||
<row><gap size="0.1"/></row>
|
|
||||||
|
|
||||||
<row>
|
|
||||||
<key size="2.1" class="shift">
|
|
||||||
<cap modifier="shift" keysym="0xFFE1">Shift</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>z</cap>
|
|
||||||
<cap if="caps">Z</cap>
|
|
||||||
<cap if="shift">Z</cap>
|
|
||||||
<cap if="caps,shift">z</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>x</cap>
|
|
||||||
<cap if="caps">X</cap>
|
|
||||||
<cap if="shift">X</cap>
|
|
||||||
<cap if="caps,shift">x</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>c</cap>
|
|
||||||
<cap if="caps">C</cap>
|
|
||||||
<cap if="shift">C</cap>
|
|
||||||
<cap if="caps,shift">c</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>v</cap>
|
|
||||||
<cap if="caps">V</cap>
|
|
||||||
<cap if="shift">V</cap>
|
|
||||||
<cap if="caps,shift">v</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>b</cap>
|
|
||||||
<cap if="caps">B</cap>
|
|
||||||
<cap if="shift">B</cap>
|
|
||||||
<cap if="caps,shift">b</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>n</cap>
|
|
||||||
<cap if="caps">N</cap>
|
|
||||||
<cap if="shift">N</cap>
|
|
||||||
<cap if="caps,shift">n</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>m</cap>
|
|
||||||
<cap if="caps">M</cap>
|
|
||||||
<cap if="shift">M</cap>
|
|
||||||
<cap if="caps,shift">m</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>,</cap>
|
|
||||||
<cap if="shift"><</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>.</cap>
|
|
||||||
<cap if="shift">></cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap>/</cap>
|
|
||||||
<cap if="shift">?</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key size="3.1" class="shift">
|
|
||||||
<cap modifier="shift" keysym="0xFFE2">Shift</cap>
|
|
||||||
</key>
|
|
||||||
</row>
|
|
||||||
|
|
||||||
<row><gap size="0.1"/></row>
|
|
||||||
|
|
||||||
<row>
|
|
||||||
<key size="1.6" class="control">
|
|
||||||
<cap modifier="control" keysym="0xFFE3">Ctrl</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key size="1.6" class="super">
|
|
||||||
<cap modifier="super" keysym="0xFFEB">Super</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key size="1.6" class="alt">
|
|
||||||
<cap modifier="alt" keysym="0xFFE9">Alt</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key size="6.1">
|
|
||||||
<cap> </cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key size="1.6" class="alt">
|
|
||||||
<cap modifier="alt" keysym="0xFFE3">Alt</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key size="1.6" class="super">
|
|
||||||
<cap modifier="super" keysym="0xFF67">Menu</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key size="1.6" class="control">
|
|
||||||
<cap modifier="control" keysym="0xFFE4">Ctrl</cap>
|
|
||||||
</key>
|
|
||||||
</row>
|
|
||||||
</column>
|
|
||||||
<column>
|
|
||||||
<row>
|
|
||||||
<gap size="0.25"/>
|
|
||||||
</row>
|
|
||||||
</column>
|
|
||||||
<column align="center">
|
|
||||||
<row>
|
|
||||||
<key size="1.75">
|
|
||||||
<cap keysym="0xFF63">Ins</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key size="1.75">
|
|
||||||
<cap keysym="0xFF50">Home</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key size="1.75">
|
|
||||||
<cap keysym="0xFF55">PgUp</cap>
|
|
||||||
</key>
|
|
||||||
</row>
|
|
||||||
<row><gap size="0.1"/></row>
|
|
||||||
<row>
|
|
||||||
<key size="1.75">
|
|
||||||
<cap keysym="0xFFFF">Del</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key size="1.75">
|
|
||||||
<cap keysym="0xFF57">End</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key size="1.75">
|
|
||||||
<cap keysym="0xFF56">PgDn</cap>
|
|
||||||
</key>
|
|
||||||
</row>
|
|
||||||
<row>
|
|
||||||
<gap/>
|
|
||||||
</row>
|
|
||||||
<row>
|
|
||||||
<key>
|
|
||||||
<cap keysym="0xFF52">↑</cap>
|
|
||||||
</key>
|
|
||||||
</row>
|
|
||||||
<row><gap size="0.1"/></row>
|
|
||||||
<row>
|
|
||||||
<key>
|
|
||||||
<cap keysym="0xFF51">←</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap keysym="0xFF54">↓</cap>
|
|
||||||
</key>
|
|
||||||
<gap size="0.1"/>
|
|
||||||
<key>
|
|
||||||
<cap keysym="0xFF53">→</cap>
|
|
||||||
</key>
|
|
||||||
</row>
|
|
||||||
</column>
|
|
||||||
</keyboard>
|
|
@ -1,8 +0,0 @@
|
|||||||
This portion of UDS (HTML5 tunnel) is based on Apache Guacamole. The source
|
|
||||||
code that produced this war file can be obtained from:
|
|
||||||
|
|
||||||
http://openuds.org/
|
|
||||||
|
|
||||||
The Apache Guacamole source code is available from:
|
|
||||||
|
|
||||||
http://guacamole.incubator.apache.org/
|
|
@ -1,337 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 Glyptodon, Inc.
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
* THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Main Guacamole UI namespace.
|
|
||||||
* @namespace
|
|
||||||
*/
|
|
||||||
var GuacUI = GuacUI || {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new element having the given tagname and CSS class.
|
|
||||||
*/
|
|
||||||
GuacUI.createElement = function(tagname, classname) {
|
|
||||||
var new_element = document.createElement(tagname);
|
|
||||||
if (classname) new_element.className = classname;
|
|
||||||
return new_element;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new element having the given tagname, CSS class, and specified
|
|
||||||
* parent element.
|
|
||||||
*/
|
|
||||||
GuacUI.createChildElement = function(parent, tagname, classname) {
|
|
||||||
var element = GuacUI.createElement(tagname, classname);
|
|
||||||
parent.appendChild(element);
|
|
||||||
return element;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds the given CSS class to the given element.
|
|
||||||
*/
|
|
||||||
GuacUI.addClass = function(element, classname) {
|
|
||||||
|
|
||||||
// If supported, use native classlist for addClass()
|
|
||||||
if (Node.classlist)
|
|
||||||
element.classList.add(classname);
|
|
||||||
|
|
||||||
// Otherwise, simply add new class via string manipulation
|
|
||||||
else
|
|
||||||
element.className += " " + classname;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the given CSS class from the given element.
|
|
||||||
*/
|
|
||||||
GuacUI.removeClass = function(element, classname) {
|
|
||||||
|
|
||||||
// If supported, use native classlist for removeClass()
|
|
||||||
if (Node.classlist)
|
|
||||||
element.classList.remove(classname);
|
|
||||||
|
|
||||||
// Otherwise, remove class via string manipulation
|
|
||||||
else {
|
|
||||||
|
|
||||||
// Filter out classes with given name
|
|
||||||
element.className = element.className.replace(/([^ ]+)[ ]*/g,
|
|
||||||
function(match, testClassname, spaces, offset, string) {
|
|
||||||
|
|
||||||
// If same class, remove
|
|
||||||
if (testClassname == classname)
|
|
||||||
return "";
|
|
||||||
|
|
||||||
// Otherwise, allow
|
|
||||||
return match;
|
|
||||||
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
} // end if no classlist support
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Object describing the UI's level of audio support. If the user has request
|
|
||||||
* that audio be disabled, this object will pretend that audio is not
|
|
||||||
* supported.
|
|
||||||
*/
|
|
||||||
GuacUI.Audio = new (function() {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Array of all supported audio mimetypes, ordered by liklihood of
|
|
||||||
* working.
|
|
||||||
*/
|
|
||||||
this.supported = Guacamole.AudioPlayer.getSupportedTypes();
|
|
||||||
|
|
||||||
// If sound disabled, declare that no types are supported
|
|
||||||
if (GuacamoleSessionStorage.getItem("disable-sound", false))
|
|
||||||
this.supported = [];
|
|
||||||
|
|
||||||
})();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Object describing the UI's level of video support.
|
|
||||||
*/
|
|
||||||
GuacUI.Video = new (function() {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Array of all supported video mimetypes, ordered by liklihood of
|
|
||||||
* working.
|
|
||||||
*/
|
|
||||||
this.supported = Guacamole.VideoPlayer.getSupportedTypes();
|
|
||||||
|
|
||||||
})();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface object which displays the progress of a download, ultimately
|
|
||||||
* becoming a download link once complete.
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
* @param {String} filename The name the file will have once complete.
|
|
||||||
*/
|
|
||||||
GuacUI.Download = function(filename) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reference to this GuacUI.Download.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var guac_download = this;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The outer div representing the notification.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var element = GuacUI.createElement("div", "download notification");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Title bar describing the notification.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var title = GuacUI.createChildElement(element, "div", "title-bar");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Close button for removing the notification.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var close_button = GuacUI.createChildElement(title, "div", "close");
|
|
||||||
close_button.onclick = function() {
|
|
||||||
if (guac_download.onclose)
|
|
||||||
guac_download.onclose();
|
|
||||||
};
|
|
||||||
|
|
||||||
GuacUI.createChildElement(title, "div", "title").textContent =
|
|
||||||
"File Transfer";
|
|
||||||
|
|
||||||
GuacUI.createChildElement(element, "div", "caption").textContent =
|
|
||||||
filename + " ";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Progress bar and status.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var progress = GuacUI.createChildElement(element, "div", "progress");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the content of the progress indicator with the given text.
|
|
||||||
*
|
|
||||||
* @param {String} text The text to assign to the progress indicator.
|
|
||||||
*/
|
|
||||||
this.updateProgress = function(text) {
|
|
||||||
progress.textContent = text;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the content of the dialog to reflect an error condition
|
|
||||||
* represented by the given text.
|
|
||||||
*
|
|
||||||
* @param {String} text A human-readable description of the error.
|
|
||||||
*/
|
|
||||||
this.showError = function(text) {
|
|
||||||
|
|
||||||
element.removeChild(progress);
|
|
||||||
GuacUI.addClass(element, "error");
|
|
||||||
|
|
||||||
var status = GuacUI.createChildElement(element, "div", "status");
|
|
||||||
status.textContent = text;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the progress indicator and replaces it with a download button.
|
|
||||||
*/
|
|
||||||
this.complete = function() {
|
|
||||||
|
|
||||||
element.removeChild(progress);
|
|
||||||
GuacUI.addClass(element, "complete");
|
|
||||||
|
|
||||||
var download = GuacUI.createChildElement(element, "button");
|
|
||||||
download.textContent = "Download";
|
|
||||||
download.onclick = function() {
|
|
||||||
if (guac_download.ondownload)
|
|
||||||
guac_download.ondownload();
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the element representing this notification.
|
|
||||||
*/
|
|
||||||
this.getElement = function() {
|
|
||||||
return element;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the close button of this notification is clicked.
|
|
||||||
* @event
|
|
||||||
*/
|
|
||||||
this.onclose = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the download button of this notification is clicked.
|
|
||||||
* @event
|
|
||||||
*/
|
|
||||||
this.ondownload = null;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface object which displays the progress of a upload.
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
* @param {String} filename The name the file will have once complete.
|
|
||||||
*/
|
|
||||||
GuacUI.Upload = function(filename) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reference to this GuacUI.Upload.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var guac_upload = this;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The outer div representing the notification.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var element = GuacUI.createElement("div", "upload notification");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Title bar describing the notification.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var title = GuacUI.createChildElement(element, "div", "title-bar");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Close button for removing the notification.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var close_button = GuacUI.createChildElement(title, "div", "close");
|
|
||||||
close_button.onclick = function() {
|
|
||||||
if (guac_upload.onclose)
|
|
||||||
guac_upload.onclose();
|
|
||||||
};
|
|
||||||
|
|
||||||
GuacUI.createChildElement(title, "div", "title").textContent =
|
|
||||||
"File Transfer";
|
|
||||||
|
|
||||||
GuacUI.createChildElement(element, "div", "caption").textContent =
|
|
||||||
filename + " ";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Progress bar and status.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var progress = GuacUI.createChildElement(element, "div", "progress");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The actual moving bar within the progress bar.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var bar = GuacUI.createChildElement(progress, "div", "bar");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The textual readout of progress.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var progress_status = GuacUI.createChildElement(progress, "div");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the content of the progress indicator with the given text.
|
|
||||||
*
|
|
||||||
* @param {String} text The text to assign to the progress indicator.
|
|
||||||
* @param {Number} percent The overall percent complete.
|
|
||||||
*/
|
|
||||||
this.updateProgress = function(text, percent) {
|
|
||||||
progress_status.textContent = text;
|
|
||||||
bar.style.width = percent + "%";
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the content of the dialog to reflect an error condition
|
|
||||||
* represented by the given text.
|
|
||||||
*
|
|
||||||
* @param {String} text A human-readable description of the error.
|
|
||||||
*/
|
|
||||||
this.showError = function(text) {
|
|
||||||
|
|
||||||
element.removeChild(progress);
|
|
||||||
GuacUI.addClass(element, "error");
|
|
||||||
|
|
||||||
var status = GuacUI.createChildElement(element, "div", "status");
|
|
||||||
status.textContent = text;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the element representing this notification.
|
|
||||||
*/
|
|
||||||
this.getElement = function() {
|
|
||||||
return element;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the close button of this notification is clicked.
|
|
||||||
* @event
|
|
||||||
*/
|
|
||||||
this.onclose = null;
|
|
||||||
|
|
||||||
};
|
|
@ -1,30 +0,0 @@
|
|||||||
This software is licensed under the MIT/X11 license.
|
|
||||||
|
|
||||||
MIT/X11 license
|
|
||||||
---------------
|
|
||||||
|
|
||||||
Copyright © 2011 [Eli Grey][1].
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person
|
|
||||||
obtaining a copy of this software and associated documentation
|
|
||||||
files (the "Software"), to deal in the Software without
|
|
||||||
restriction, including without limitation the rights to use,
|
|
||||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the
|
|
||||||
Software is furnished to do so, subject to the following
|
|
||||||
conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be
|
|
||||||
included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
||||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
||||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
||||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
||||||
OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
|
|
||||||
[1]: http://eligrey.com
|
|
@ -1,178 +0,0 @@
|
|||||||
/* Blob.js
|
|
||||||
* A Blob implementation.
|
|
||||||
* 2013-06-20
|
|
||||||
*
|
|
||||||
* By Eli Grey, http://eligrey.com
|
|
||||||
* By Devin Samarin, https://github.com/eboyjr
|
|
||||||
* License: X11/MIT
|
|
||||||
* See LICENSE.md
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*global self, unescape */
|
|
||||||
/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true,
|
|
||||||
plusplus: true */
|
|
||||||
|
|
||||||
/*! @source http://purl.eligrey.com/github/Blob.js/blob/master/Blob.js */
|
|
||||||
|
|
||||||
if (typeof Blob !== "function" || typeof URL === "undefined")
|
|
||||||
if (typeof Blob === "function" && typeof webkitURL !== "undefined") var URL = webkitURL;
|
|
||||||
else var Blob = (function (view) {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
var BlobBuilder = view.BlobBuilder || view.WebKitBlobBuilder || view.MozBlobBuilder || view.MSBlobBuilder || (function(view) {
|
|
||||||
var
|
|
||||||
get_class = function(object) {
|
|
||||||
return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1];
|
|
||||||
}
|
|
||||||
, FakeBlobBuilder = function BlobBuilder() {
|
|
||||||
this.data = [];
|
|
||||||
}
|
|
||||||
, FakeBlob = function Blob(data, type, encoding) {
|
|
||||||
this.data = data;
|
|
||||||
this.size = data.length;
|
|
||||||
this.type = type;
|
|
||||||
this.encoding = encoding;
|
|
||||||
}
|
|
||||||
, FBB_proto = FakeBlobBuilder.prototype
|
|
||||||
, FB_proto = FakeBlob.prototype
|
|
||||||
, FileReaderSync = view.FileReaderSync
|
|
||||||
, FileException = function(type) {
|
|
||||||
this.code = this[this.name = type];
|
|
||||||
}
|
|
||||||
, file_ex_codes = (
|
|
||||||
"NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR "
|
|
||||||
+ "NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR"
|
|
||||||
).split(" ")
|
|
||||||
, file_ex_code = file_ex_codes.length
|
|
||||||
, real_URL = view.URL || view.webkitURL || view
|
|
||||||
, real_create_object_URL = real_URL.createObjectURL
|
|
||||||
, real_revoke_object_URL = real_URL.revokeObjectURL
|
|
||||||
, URL = real_URL
|
|
||||||
, btoa = view.btoa
|
|
||||||
, atob = view.atob
|
|
||||||
, can_apply_typed_arrays = false
|
|
||||||
, can_apply_typed_arrays_test = function(pass) {
|
|
||||||
can_apply_typed_arrays = !pass;
|
|
||||||
}
|
|
||||||
|
|
||||||
, ArrayBuffer = view.ArrayBuffer
|
|
||||||
, Uint8Array = view.Uint8Array
|
|
||||||
;
|
|
||||||
FakeBlob.fake = FB_proto.fake = true;
|
|
||||||
while (file_ex_code--) {
|
|
||||||
FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (Uint8Array) {
|
|
||||||
can_apply_typed_arrays_test.apply(0, new Uint8Array(1));
|
|
||||||
}
|
|
||||||
} catch (ex) {}
|
|
||||||
if (!real_URL.createObjectURL) {
|
|
||||||
URL = view.URL = {};
|
|
||||||
}
|
|
||||||
URL.createObjectURL = function(blob) {
|
|
||||||
var
|
|
||||||
type = blob.type
|
|
||||||
, data_URI_header
|
|
||||||
;
|
|
||||||
if (type === null) {
|
|
||||||
type = "application/octet-stream";
|
|
||||||
}
|
|
||||||
if (blob instanceof FakeBlob) {
|
|
||||||
data_URI_header = "data:" + type;
|
|
||||||
if (blob.encoding === "base64") {
|
|
||||||
return data_URI_header + ";base64," + blob.data;
|
|
||||||
} else if (blob.encoding === "URI") {
|
|
||||||
return data_URI_header + "," + decodeURIComponent(blob.data);
|
|
||||||
} if (btoa) {
|
|
||||||
return data_URI_header + ";base64," + btoa(blob.data);
|
|
||||||
} else {
|
|
||||||
return data_URI_header + "," + encodeURIComponent(blob.data);
|
|
||||||
}
|
|
||||||
} else if (real_create_object_URL) {
|
|
||||||
return real_create_object_URL.call(real_URL, blob);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
URL.revokeObjectURL = function(object_URL) {
|
|
||||||
if (object_URL.substring(0, 5) !== "data:" && real_revoke_object_URL) {
|
|
||||||
real_revoke_object_URL.call(real_URL, object_URL);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
FBB_proto.append = function(data/*, endings*/) {
|
|
||||||
var bb = this.data;
|
|
||||||
// decode data to a binary string
|
|
||||||
if (Uint8Array && (data instanceof ArrayBuffer || data instanceof Uint8Array)) {
|
|
||||||
if (can_apply_typed_arrays) {
|
|
||||||
bb.push(String.fromCharCode.apply(String, new Uint8Array(data)));
|
|
||||||
} else {
|
|
||||||
var
|
|
||||||
str = ""
|
|
||||||
, buf = new Uint8Array(data)
|
|
||||||
, i = 0
|
|
||||||
, buf_len = buf.length
|
|
||||||
;
|
|
||||||
for (; i < buf_len; i++) {
|
|
||||||
str += String.fromCharCode(buf[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (get_class(data) === "Blob" || get_class(data) === "File") {
|
|
||||||
if (FileReaderSync) {
|
|
||||||
var fr = new FileReaderSync;
|
|
||||||
bb.push(fr.readAsBinaryString(data));
|
|
||||||
} else {
|
|
||||||
// async FileReader won't work as BlobBuilder is sync
|
|
||||||
throw new FileException("NOT_READABLE_ERR");
|
|
||||||
}
|
|
||||||
} else if (data instanceof FakeBlob) {
|
|
||||||
if (data.encoding === "base64" && atob) {
|
|
||||||
bb.push(atob(data.data));
|
|
||||||
} else if (data.encoding === "URI") {
|
|
||||||
bb.push(decodeURIComponent(data.data));
|
|
||||||
} else if (data.encoding === "raw") {
|
|
||||||
bb.push(data.data);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (typeof data !== "string") {
|
|
||||||
data += ""; // convert unsupported types to strings
|
|
||||||
}
|
|
||||||
// decode UTF-16 to binary string
|
|
||||||
bb.push(unescape(encodeURIComponent(data)));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
FBB_proto.getBlob = function(type) {
|
|
||||||
if (!arguments.length) {
|
|
||||||
type = null;
|
|
||||||
}
|
|
||||||
return new FakeBlob(this.data.join(""), type, "raw");
|
|
||||||
};
|
|
||||||
FBB_proto.toString = function() {
|
|
||||||
return "[object BlobBuilder]";
|
|
||||||
};
|
|
||||||
FB_proto.slice = function(start, end, type) {
|
|
||||||
var args = arguments.length;
|
|
||||||
if (args < 3) {
|
|
||||||
type = null;
|
|
||||||
}
|
|
||||||
return new FakeBlob(
|
|
||||||
this.data.slice(start, args > 1 ? end : this.data.length)
|
|
||||||
, type
|
|
||||||
, this.encoding
|
|
||||||
);
|
|
||||||
};
|
|
||||||
FB_proto.toString = function() {
|
|
||||||
return "[object Blob]";
|
|
||||||
};
|
|
||||||
return FakeBlobBuilder;
|
|
||||||
}(view));
|
|
||||||
|
|
||||||
return function Blob(blobParts, options) {
|
|
||||||
var type = options ? (options.type || "") : "";
|
|
||||||
var builder = new BlobBuilder();
|
|
||||||
if (blobParts) {
|
|
||||||
for (var i = 0, len = blobParts.length; i < len; i++) {
|
|
||||||
builder.append(blobParts[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return builder.getBlob(type);
|
|
||||||
};
|
|
||||||
}(self));
|
|
@ -1,30 +0,0 @@
|
|||||||
This software is licensed under the MIT/X11 license.
|
|
||||||
|
|
||||||
MIT/X11 license
|
|
||||||
---------------
|
|
||||||
|
|
||||||
Copyright © 2011 [Eli Grey][1].
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person
|
|
||||||
obtaining a copy of this software and associated documentation
|
|
||||||
files (the "Software"), to deal in the Software without
|
|
||||||
restriction, including without limitation the rights to use,
|
|
||||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the
|
|
||||||
Software is furnished to do so, subject to the following
|
|
||||||
conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be
|
|
||||||
included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
||||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
||||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
||||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
||||||
OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
|
|
||||||
[1]: http://eligrey.com
|
|
@ -1,216 +0,0 @@
|
|||||||
/* FileSaver.js
|
|
||||||
* A saveAs() FileSaver implementation.
|
|
||||||
* 2013-01-23
|
|
||||||
*
|
|
||||||
* By Eli Grey, http://eligrey.com
|
|
||||||
* License: X11/MIT
|
|
||||||
* See LICENSE.md
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*global self */
|
|
||||||
/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true,
|
|
||||||
plusplus: true */
|
|
||||||
|
|
||||||
/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */
|
|
||||||
|
|
||||||
var saveAs = saveAs
|
|
||||||
|| (navigator.msSaveBlob && navigator.msSaveBlob.bind(navigator))
|
|
||||||
|| (function(view) {
|
|
||||||
"use strict";
|
|
||||||
var
|
|
||||||
doc = view.document
|
|
||||||
// only get URL when necessary in case BlobBuilder.js hasn't overridden it yet
|
|
||||||
, get_URL = function() {
|
|
||||||
return view.URL || view.webkitURL || view;
|
|
||||||
}
|
|
||||||
, URL = view.URL || view.webkitURL || view
|
|
||||||
, save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a")
|
|
||||||
, can_use_save_link = "download" in save_link
|
|
||||||
, click = function(node) {
|
|
||||||
var event = doc.createEvent("MouseEvents");
|
|
||||||
event.initMouseEvent(
|
|
||||||
"click", true, false, view, 0, 0, 0, 0, 0
|
|
||||||
, false, false, false, false, 0, null
|
|
||||||
);
|
|
||||||
node.dispatchEvent(event);
|
|
||||||
}
|
|
||||||
, webkit_req_fs = view.webkitRequestFileSystem
|
|
||||||
, req_fs = view.requestFileSystem || webkit_req_fs || view.mozRequestFileSystem
|
|
||||||
, throw_outside = function (ex) {
|
|
||||||
(view.setImmediate || view.setTimeout)(function() {
|
|
||||||
throw ex;
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
, force_saveable_type = "application/octet-stream"
|
|
||||||
, fs_min_size = 0
|
|
||||||
, deletion_queue = []
|
|
||||||
, process_deletion_queue = function() {
|
|
||||||
var i = deletion_queue.length;
|
|
||||||
while (i--) {
|
|
||||||
var file = deletion_queue[i];
|
|
||||||
if (typeof file === "string") { // file is an object URL
|
|
||||||
URL.revokeObjectURL(file);
|
|
||||||
} else { // file is a File
|
|
||||||
file.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
deletion_queue.length = 0; // clear queue
|
|
||||||
}
|
|
||||||
, dispatch = function(filesaver, event_types, event) {
|
|
||||||
event_types = [].concat(event_types);
|
|
||||||
var i = event_types.length;
|
|
||||||
while (i--) {
|
|
||||||
var listener = filesaver["on" + event_types[i]];
|
|
||||||
if (typeof listener === "function") {
|
|
||||||
try {
|
|
||||||
listener.call(filesaver, event || filesaver);
|
|
||||||
} catch (ex) {
|
|
||||||
throw_outside(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
, FileSaver = function(blob, name) {
|
|
||||||
// First try a.download, then web filesystem, then object URLs
|
|
||||||
var
|
|
||||||
filesaver = this
|
|
||||||
, type = blob.type
|
|
||||||
, blob_changed = false
|
|
||||||
, object_url
|
|
||||||
, target_view
|
|
||||||
, get_object_url = function() {
|
|
||||||
var object_url = get_URL().createObjectURL(blob);
|
|
||||||
deletion_queue.push(object_url);
|
|
||||||
return object_url;
|
|
||||||
}
|
|
||||||
, dispatch_all = function() {
|
|
||||||
dispatch(filesaver, "writestart progress write writeend".split(" "));
|
|
||||||
}
|
|
||||||
// on any filesys errors revert to saving with object URLs
|
|
||||||
, fs_error = function() {
|
|
||||||
// don't create more object URLs than needed
|
|
||||||
if (blob_changed || !object_url) {
|
|
||||||
object_url = get_object_url(blob);
|
|
||||||
}
|
|
||||||
if (target_view) {
|
|
||||||
target_view.location.href = object_url;
|
|
||||||
} else {
|
|
||||||
window.open(object_url, "_blank");
|
|
||||||
}
|
|
||||||
filesaver.readyState = filesaver.DONE;
|
|
||||||
dispatch_all();
|
|
||||||
}
|
|
||||||
, abortable = function(func) {
|
|
||||||
return function() {
|
|
||||||
if (filesaver.readyState !== filesaver.DONE) {
|
|
||||||
return func.apply(this, arguments);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
, create_if_not_found = {create: true, exclusive: false}
|
|
||||||
, slice
|
|
||||||
;
|
|
||||||
filesaver.readyState = filesaver.INIT;
|
|
||||||
if (!name) {
|
|
||||||
name = "download";
|
|
||||||
}
|
|
||||||
if (can_use_save_link) {
|
|
||||||
object_url = get_object_url(blob);
|
|
||||||
save_link.href = object_url;
|
|
||||||
save_link.download = name;
|
|
||||||
click(save_link);
|
|
||||||
filesaver.readyState = filesaver.DONE;
|
|
||||||
dispatch_all();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Object and web filesystem URLs have a problem saving in Google Chrome when
|
|
||||||
// viewed in a tab, so I force save with application/octet-stream
|
|
||||||
// http://code.google.com/p/chromium/issues/detail?id=91158
|
|
||||||
if (view.chrome && type && type !== force_saveable_type) {
|
|
||||||
slice = blob.slice || blob.webkitSlice;
|
|
||||||
blob = slice.call(blob, 0, blob.size, force_saveable_type);
|
|
||||||
blob_changed = true;
|
|
||||||
}
|
|
||||||
// Since I can't be sure that the guessed media type will trigger a download
|
|
||||||
// in WebKit, I append .download to the filename.
|
|
||||||
// https://bugs.webkit.org/show_bug.cgi?id=65440
|
|
||||||
if (webkit_req_fs && name !== "download") {
|
|
||||||
name += ".download";
|
|
||||||
}
|
|
||||||
if (type === force_saveable_type || webkit_req_fs) {
|
|
||||||
target_view = view;
|
|
||||||
}
|
|
||||||
if (!req_fs) {
|
|
||||||
fs_error();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fs_min_size += blob.size;
|
|
||||||
req_fs(view.TEMPORARY, fs_min_size, abortable(function(fs) {
|
|
||||||
fs.root.getDirectory("saved", create_if_not_found, abortable(function(dir) {
|
|
||||||
var save = function() {
|
|
||||||
dir.getFile(name, create_if_not_found, abortable(function(file) {
|
|
||||||
file.createWriter(abortable(function(writer) {
|
|
||||||
writer.onwriteend = function(event) {
|
|
||||||
target_view.location.href = file.toURL();
|
|
||||||
deletion_queue.push(file);
|
|
||||||
filesaver.readyState = filesaver.DONE;
|
|
||||||
dispatch(filesaver, "writeend", event);
|
|
||||||
};
|
|
||||||
writer.onerror = function() {
|
|
||||||
var error = writer.error;
|
|
||||||
if (error.code !== error.ABORT_ERR) {
|
|
||||||
fs_error();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
"writestart progress write abort".split(" ").forEach(function(event) {
|
|
||||||
writer["on" + event] = filesaver["on" + event];
|
|
||||||
});
|
|
||||||
writer.write(blob);
|
|
||||||
filesaver.abort = function() {
|
|
||||||
writer.abort();
|
|
||||||
filesaver.readyState = filesaver.DONE;
|
|
||||||
};
|
|
||||||
filesaver.readyState = filesaver.WRITING;
|
|
||||||
}), fs_error);
|
|
||||||
}), fs_error);
|
|
||||||
};
|
|
||||||
dir.getFile(name, {create: false}, abortable(function(file) {
|
|
||||||
// delete file if it already exists
|
|
||||||
file.remove();
|
|
||||||
save();
|
|
||||||
}), abortable(function(ex) {
|
|
||||||
if (ex.code === ex.NOT_FOUND_ERR) {
|
|
||||||
save();
|
|
||||||
} else {
|
|
||||||
fs_error();
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}), fs_error);
|
|
||||||
}), fs_error);
|
|
||||||
}
|
|
||||||
, FS_proto = FileSaver.prototype
|
|
||||||
, saveAs = function(blob, name) {
|
|
||||||
return new FileSaver(blob, name);
|
|
||||||
}
|
|
||||||
;
|
|
||||||
FS_proto.abort = function() {
|
|
||||||
var filesaver = this;
|
|
||||||
filesaver.readyState = filesaver.DONE;
|
|
||||||
dispatch(filesaver, "abort");
|
|
||||||
};
|
|
||||||
FS_proto.readyState = FS_proto.INIT = 0;
|
|
||||||
FS_proto.WRITING = 1;
|
|
||||||
FS_proto.DONE = 2;
|
|
||||||
|
|
||||||
FS_proto.error =
|
|
||||||
FS_proto.onwritestart =
|
|
||||||
FS_proto.onprogress =
|
|
||||||
FS_proto.onwrite =
|
|
||||||
FS_proto.onabort =
|
|
||||||
FS_proto.onerror =
|
|
||||||
FS_proto.onwriteend =
|
|
||||||
null;
|
|
||||||
|
|
||||||
view.addEventListener("unload", process_deletion_queue, false);
|
|
||||||
return saveAs;
|
|
||||||
}(self));
|
|
@ -1,160 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 Glyptodon, Inc.
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
* THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Global storage for Guacamole pages.
|
|
||||||
*/
|
|
||||||
GuacamoleSessionStorage = new (function() {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The contents of storage, as a JSON string containing name/value pairs as
|
|
||||||
* properties.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @type String
|
|
||||||
*/
|
|
||||||
var stored_json = "{}";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called whenever an item value changes.
|
|
||||||
*
|
|
||||||
* @callback onchange
|
|
||||||
* @param {String} name The name of the item changed.
|
|
||||||
* @param value The new item value.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All attached listeners.
|
|
||||||
*
|
|
||||||
* @type onchange[]
|
|
||||||
*/
|
|
||||||
var listeners = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notifies all listeners that an item has changed.
|
|
||||||
*
|
|
||||||
* @param {String} name The name of the item that changed.
|
|
||||||
* @param value The new item value.
|
|
||||||
*/
|
|
||||||
function __notify_changed(name, value) {
|
|
||||||
for (var i=0; i<listeners.length; i++)
|
|
||||||
listeners[i](name, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the value stored within the item having the given name.
|
|
||||||
*
|
|
||||||
* @param {String} name The name of the item to read.
|
|
||||||
* @param [value] The default value, if any.
|
|
||||||
* @return The value of the given item.
|
|
||||||
*/
|
|
||||||
this.getItem = function(name, value) {
|
|
||||||
|
|
||||||
// Attempt to read JSON from localStorage, default to local variable
|
|
||||||
var json = stored_json;
|
|
||||||
if (window.localStorage) {
|
|
||||||
try {
|
|
||||||
json = localStorage.getItem("GUACAMOLE_STATE") || "{}";
|
|
||||||
}
|
|
||||||
catch (ignore) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
var obj = JSON.parse(json);
|
|
||||||
if (obj[name] !== undefined)
|
|
||||||
return obj[name];
|
|
||||||
|
|
||||||
return value;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the item having the given name to the given value.
|
|
||||||
*
|
|
||||||
* @param {String} name The name of the item to change.
|
|
||||||
* @param [value] An arbitrary value.
|
|
||||||
*/
|
|
||||||
this.setItem = function(name, value) {
|
|
||||||
|
|
||||||
// Attempt to read JSON from localStorage, default to local variable
|
|
||||||
var json = stored_json;
|
|
||||||
if (window.localStorage) {
|
|
||||||
try {
|
|
||||||
json = localStorage.getItem("GUACAMOLE_STATE") || "{}";
|
|
||||||
}
|
|
||||||
catch (ignore) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Modify object property
|
|
||||||
var obj = JSON.parse(json);
|
|
||||||
var old = obj[name];
|
|
||||||
obj[name] = value;
|
|
||||||
|
|
||||||
// Notify of change
|
|
||||||
if (old !== value)
|
|
||||||
__notify_changed(name, value);
|
|
||||||
|
|
||||||
// Attempt to set JSON within localStorage, default to local variable
|
|
||||||
stored_json = JSON.stringify(obj);
|
|
||||||
if (window.localStorage) {
|
|
||||||
try {
|
|
||||||
localStorage.setItem("GUACAMOLE_STATE", stored_json);
|
|
||||||
}
|
|
||||||
catch (ignore) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
// Reload when modified
|
|
||||||
window.addEventListener("storage", function reload() {
|
|
||||||
|
|
||||||
// Pull current state
|
|
||||||
var new_json = localStorage.getItem("GUACAMOLE_STATE") || "{}";
|
|
||||||
|
|
||||||
var new_state = JSON.parse(new_json);
|
|
||||||
var old_state = JSON.parse(stored_json);
|
|
||||||
|
|
||||||
// Check if any values are different
|
|
||||||
for (var name in new_state) {
|
|
||||||
|
|
||||||
// If value changed, notify
|
|
||||||
var old = old_state[name];
|
|
||||||
if (old !== new_state[name])
|
|
||||||
__notify_changed(name, new_state[name]);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
stored_json = new_json;
|
|
||||||
|
|
||||||
}, false);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensures that the given function will be called for each change in
|
|
||||||
* item value. The function must accept a single argument which will be
|
|
||||||
* the name of the item changed.
|
|
||||||
*
|
|
||||||
* @param {onchange} onchange The function to call when an item changes.
|
|
||||||
*/
|
|
||||||
this.addChangeListener = function(onchange) {
|
|
||||||
listeners.push(onchange);
|
|
||||||
};
|
|
||||||
|
|
||||||
})();
|
|
@ -1,53 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 Glyptodon, Inc.
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
* THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* fadein: Fade from fully transparent to fully opaque.
|
|
||||||
*/
|
|
||||||
@keyframes fadein {
|
|
||||||
from { opacity: 0; }
|
|
||||||
to { opacity: 1; }
|
|
||||||
}
|
|
||||||
@-moz-keyframes fadein {
|
|
||||||
from { opacity: 0; }
|
|
||||||
to { opacity: 1; }
|
|
||||||
}
|
|
||||||
@-webkit-keyframes fadein {
|
|
||||||
from { opacity: 0; }
|
|
||||||
to { opacity: 1; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* fadeout: Fade from fully opaque to fully transparent.
|
|
||||||
*/
|
|
||||||
@keyframes fadeout {
|
|
||||||
from { opacity: 1; }
|
|
||||||
to { opacity: 0; }
|
|
||||||
}
|
|
||||||
@-moz-keyframes fadeout {
|
|
||||||
from { opacity: 1; }
|
|
||||||
to { opacity: 0; }
|
|
||||||
}
|
|
||||||
@-webkit-keyframes fadeout {
|
|
||||||
from { opacity: 1; }
|
|
||||||
to { opacity: 0; }
|
|
||||||
}
|
|
@ -1,779 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 Glyptodon, Inc.
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
* THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
body {
|
|
||||||
background: black;
|
|
||||||
font-family: FreeSans, Helvetica, Arial, sans-serif;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.software-cursor {
|
|
||||||
cursor: url('../images/mouse/blank.gif'),url('../images/mouse/blank.cur'),default;
|
|
||||||
overflow: hidden;
|
|
||||||
cursor: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.guac-error .software-cursor {
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
* {
|
|
||||||
-webkit-tap-highlight-color: rgba(0,0,0,0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.event-target {
|
|
||||||
position: fixed;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dialogs */
|
|
||||||
|
|
||||||
div.dialogOuter {
|
|
||||||
display: table;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
position: fixed;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
background: rgba(0, 0, 0, 0.75);
|
|
||||||
}
|
|
||||||
|
|
||||||
div.dialogMiddle {
|
|
||||||
width: 100%;
|
|
||||||
text-align: center;
|
|
||||||
display: table-cell;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
|
|
||||||
border-style: solid;
|
|
||||||
border-width: 1px;
|
|
||||||
|
|
||||||
padding: 0.25em;
|
|
||||||
padding-right: 1em;
|
|
||||||
padding-left: 1em;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
button:active {
|
|
||||||
|
|
||||||
padding-top: 0.35em;
|
|
||||||
padding-left: 1.1em;
|
|
||||||
|
|
||||||
padding-bottom: 0.15em;
|
|
||||||
padding-right: 0.9em;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
button#reconnect {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.guac-error button#reconnect {
|
|
||||||
display: inline;
|
|
||||||
|
|
||||||
background: #200;
|
|
||||||
border-color: #822;
|
|
||||||
color: #944;
|
|
||||||
}
|
|
||||||
|
|
||||||
.guac-error button#reconnect:hover {
|
|
||||||
background: #822;
|
|
||||||
border-color: #B33;
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
div.dialog p {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div#main {
|
|
||||||
overflow: auto;
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.displayOuter {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
display: table;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.displayMiddle {
|
|
||||||
width: 100%;
|
|
||||||
display: table-cell;
|
|
||||||
vertical-align: middle;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
div#display * {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
div#display > * {
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.magnifier-background {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
z-index: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.magnifier {
|
|
||||||
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
|
|
||||||
box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.75);
|
|
||||||
width: 50%;
|
|
||||||
height: 50%;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.pan-overlay,
|
|
||||||
.type-overlay {
|
|
||||||
position: fixed;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pan-overlay .indicator {
|
|
||||||
position: fixed;
|
|
||||||
background-size: 32px 32px;
|
|
||||||
-moz-background-size: 32px 32px;
|
|
||||||
-webkit-background-size: 32px 32px;
|
|
||||||
-khtml-background-size: 32px 32px;
|
|
||||||
background-position: center;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pan-overlay .indicator.up {
|
|
||||||
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
height: 32px;
|
|
||||||
|
|
||||||
background-image: url('../images/arrows/arrows-u.png');
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.pan-overlay .indicator.down {
|
|
||||||
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
height: 32px;
|
|
||||||
|
|
||||||
background-image: url('../images/arrows/arrows-d.png');
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.pan-overlay .indicator.left {
|
|
||||||
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 32px;
|
|
||||||
|
|
||||||
background-image: url('../images/arrows/arrows-l.png');
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.pan-overlay .indicator.right {
|
|
||||||
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
right: 0;
|
|
||||||
width: 32px;
|
|
||||||
|
|
||||||
background-image: url('../images/arrows/arrows-r.png');
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Viewport Clone */
|
|
||||||
|
|
||||||
div#viewportClone {
|
|
||||||
display: table;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
position: fixed;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes show-dialog {
|
|
||||||
0% {transform: scale(0.75); }
|
|
||||||
100% {transform: scale(1); }
|
|
||||||
}
|
|
||||||
|
|
||||||
@-webkit-keyframes show-dialog {
|
|
||||||
0% {-webkit-transform: scale(0.75); }
|
|
||||||
100% {-webkit-transform: scale(1); }
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog {
|
|
||||||
|
|
||||||
animation-name: show-dialog;
|
|
||||||
animation-timing-function: linear;
|
|
||||||
animation-duration: 0.125s;
|
|
||||||
-webkit-animation-name: show-dialog;
|
|
||||||
-webkit-animation-timing-function: linear;
|
|
||||||
-webkit-animation-duration: 0.125s;
|
|
||||||
|
|
||||||
max-width: 75%;
|
|
||||||
max-height: none;
|
|
||||||
width: 4in;
|
|
||||||
-moz-border-radius: 0.2em;
|
|
||||||
-webkit-border-radius: 0.2em;
|
|
||||||
-khtml-border-radius: 0.2em;
|
|
||||||
border-radius: 0.2em;
|
|
||||||
|
|
||||||
padding: 0.5em;
|
|
||||||
text-align: left;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.guac-error .dialog {
|
|
||||||
background: #FDD;
|
|
||||||
border: 1px solid #964040;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog .title {
|
|
||||||
font-size: 1.1em;
|
|
||||||
font-weight: bold;
|
|
||||||
border-bottom: 1px solid black;
|
|
||||||
margin-bottom: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog .status, .dialog .countdown, .dialog .reconnect {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0.5em;
|
|
||||||
font-size: 0.8em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog .reconnect {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.guac-error .dialog .reconnect {
|
|
||||||
display: block;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.hint {
|
|
||||||
|
|
||||||
border: 0.25em solid rgba(255, 255, 255, 0.25);
|
|
||||||
background: black;
|
|
||||||
opacity: 0.75;
|
|
||||||
|
|
||||||
color: white;
|
|
||||||
|
|
||||||
max-width: 10em;
|
|
||||||
padding: 1em;
|
|
||||||
margin: 1em;
|
|
||||||
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
|
|
||||||
box-shadow: 0.25em 0.25em 0.25em rgba(0, 0, 0, 0.75);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#notificationArea {
|
|
||||||
position: fixed;
|
|
||||||
right: 0.5em;
|
|
||||||
bottom: 0.5em;
|
|
||||||
max-width: 25%;
|
|
||||||
min-width: 10em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification {
|
|
||||||
|
|
||||||
font-size: 0.7em;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.75);
|
|
||||||
-moz-border-radius: 0.2em;
|
|
||||||
-webkit-border-radius: 0.2em;
|
|
||||||
-khtml-border-radius: 0.2em;
|
|
||||||
border-radius: 0.2em;
|
|
||||||
background: white;
|
|
||||||
|
|
||||||
color: black;
|
|
||||||
|
|
||||||
padding: 0.5em;
|
|
||||||
margin: 1em;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
box-shadow: 0.1em 0.1em 0.2em rgba(0, 0, 0, 0.25);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification div {
|
|
||||||
display: inline-block;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification .title-bar {
|
|
||||||
display: block;
|
|
||||||
white-space: nowrap;
|
|
||||||
font-weight: bold;
|
|
||||||
|
|
||||||
border-bottom: 1px solid black;
|
|
||||||
padding-bottom: 0.5em;
|
|
||||||
margin-bottom: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification .title-bar * {
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification .close {
|
|
||||||
|
|
||||||
background: url('../images/action-icons/guac-close.png');
|
|
||||||
background-size: 10px 10px;
|
|
||||||
-moz-background-size: 10px 10px;
|
|
||||||
-webkit-background-size: 10px 10px;
|
|
||||||
-khtml-background-size: 10px 10px;
|
|
||||||
|
|
||||||
width: 10px;
|
|
||||||
height: 10px;
|
|
||||||
|
|
||||||
float: right;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes progress {
|
|
||||||
from {background-position: 0px 0px;}
|
|
||||||
to {background-position: 64px 0px;}
|
|
||||||
}
|
|
||||||
|
|
||||||
@-webkit-keyframes progress {
|
|
||||||
from {background-position: 0px 0px;}
|
|
||||||
to {background-position: 64px 0px;}
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification .caption,
|
|
||||||
.download.notification .caption {
|
|
||||||
width: 100%;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload.notification .status,
|
|
||||||
.download.notification .status {
|
|
||||||
color: red;
|
|
||||||
font-size: 1em;
|
|
||||||
padding: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.download.notification .progress,
|
|
||||||
.upload.notification .progress,
|
|
||||||
.download.notification .download {
|
|
||||||
|
|
||||||
margin-top: 1em;
|
|
||||||
margin-left: 0.75em;
|
|
||||||
padding: 0.25em;
|
|
||||||
min-width: 5em;
|
|
||||||
|
|
||||||
border: 1px solid gray;
|
|
||||||
-moz-border-radius: 0.2em;
|
|
||||||
-webkit-border-radius: 0.2em;
|
|
||||||
-khtml-border-radius: 0.2em;
|
|
||||||
border-radius: 0.2em;
|
|
||||||
|
|
||||||
text-align: center;
|
|
||||||
float: right;
|
|
||||||
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload.notification .progress {
|
|
||||||
float: none;
|
|
||||||
width: 80%;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.download.notification .progress div,
|
|
||||||
.upload.notification .progress div {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.download.notification .progress .bar,
|
|
||||||
.upload.notification .progress .bar {
|
|
||||||
background: #A3D655;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
height: 100%;
|
|
||||||
width: 0;
|
|
||||||
box-shadow: inset 1px 1px 0 rgba(255, 255, 255, 0.5),
|
|
||||||
inset -1px -1px 0 rgba( 0, 0, 0, 0.1),
|
|
||||||
1px 1px 0 gray;
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload.notification .progress,
|
|
||||||
.download.notification .progress {
|
|
||||||
|
|
||||||
background: #C2C2C2 url('../images/progress.png');
|
|
||||||
background-size: 16px 16px;
|
|
||||||
-moz-background-size: 16px 16px;
|
|
||||||
-webkit-background-size: 16px 16px;
|
|
||||||
-khtml-background-size: 16px 16px;
|
|
||||||
|
|
||||||
animation-name: progress;
|
|
||||||
animation-duration: 2s;
|
|
||||||
animation-timing-function: linear;
|
|
||||||
animation-iteration-count: infinite;
|
|
||||||
|
|
||||||
-webkit-animation-name: progress;
|
|
||||||
-webkit-animation-duration: 2s;
|
|
||||||
-webkit-animation-timing-function: linear;
|
|
||||||
-webkit-animation-iteration-count: infinite;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.download.notification .download {
|
|
||||||
background: rgb(16, 87, 153);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
#preload {
|
|
||||||
visibility: hidden;
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Menu */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* show-menu: Animation for showing the menu
|
|
||||||
*/
|
|
||||||
@keyframes show-menu {
|
|
||||||
from { left: -480px; opacity: 0;}
|
|
||||||
to { left: 0; opacity: 1;}
|
|
||||||
}
|
|
||||||
@-moz-keyframes show-menu {
|
|
||||||
from { left: -480px; opacity: 0;}
|
|
||||||
to { left: 0; opacity: 1;}
|
|
||||||
}
|
|
||||||
@-webkit-keyframes show-menu {
|
|
||||||
from { left: -480px; opacity: 0;}
|
|
||||||
to { left: 0; opacity: 1;}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* hide-menu: Animation for hiding the menu
|
|
||||||
*/
|
|
||||||
@keyframes hide-menu {
|
|
||||||
from { left: 0; opacity: 1;}
|
|
||||||
to { left: -480px; opacity: 0;}
|
|
||||||
}
|
|
||||||
@-moz-keyframes hide-menu {
|
|
||||||
from { left: 0; opacity: 1;}
|
|
||||||
to { left: -480px; opacity: 0;}
|
|
||||||
}
|
|
||||||
@-webkit-keyframes hide-menu {
|
|
||||||
from { left: 0; opacity: 1;}
|
|
||||||
to { left: -480px; opacity: 0;}
|
|
||||||
}
|
|
||||||
|
|
||||||
#menu {
|
|
||||||
overflow: auto;
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
max-width: 100%;
|
|
||||||
width: 480px;
|
|
||||||
background: #EEE;
|
|
||||||
box-shadow: inset -1px 0 2px white, 1px 0 2px black;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
#menu .content {
|
|
||||||
padding: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#menu .content > * {
|
|
||||||
margin: 1em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#menu, #menu.closed {
|
|
||||||
left: -480px;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#menu.open {
|
|
||||||
left: 0px;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
#menu.closed {
|
|
||||||
animation-name: hide-menu;
|
|
||||||
animation-timing-function: linear;
|
|
||||||
animation-duration: 0.05s;
|
|
||||||
-webkit-animation-name: hide-menu;
|
|
||||||
-webkit-animation-timing-function: linear;
|
|
||||||
-webkit-animation-duration: 0.05s;
|
|
||||||
}
|
|
||||||
|
|
||||||
#menu.open {
|
|
||||||
animation-name: show-menu;
|
|
||||||
animation-timing-function: linear;
|
|
||||||
animation-duration: 0.05s;
|
|
||||||
-webkit-animation-name: show-menu;
|
|
||||||
-webkit-animation-timing-function: linear;
|
|
||||||
-webkit-animation-duration: 0.05s;
|
|
||||||
}
|
|
||||||
|
|
||||||
#clipboard-settings textarea {
|
|
||||||
width: 100%;
|
|
||||||
border: 1px solid #AAA;
|
|
||||||
-moz-border-radius: 0.25em;
|
|
||||||
-webkit-border-radius: 0.25em;
|
|
||||||
-khtml-border-radius: 0.25em;
|
|
||||||
border-radius: 0.25em;
|
|
||||||
white-space: pre;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
#mouse-settings .choice {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#mouse-settings .choice .figure {
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
max-width: 80%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#keyboard-settings .caption {
|
|
||||||
font-size: 0.9em;
|
|
||||||
margin-left: 2em;
|
|
||||||
margin-right: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#mouse-settings .figure .caption {
|
|
||||||
text-align: center;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#mouse-settings .figure img {
|
|
||||||
display: block;
|
|
||||||
max-width: 100%;
|
|
||||||
margin: 1em auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
#menu h3 {
|
|
||||||
|
|
||||||
background: rgba(0, 0, 0, 0.4);
|
|
||||||
|
|
||||||
padding: 0.25em 0.5em;
|
|
||||||
margin: 0;
|
|
||||||
font-size: 1em;
|
|
||||||
|
|
||||||
color: white;
|
|
||||||
font-weight: bold;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#keyboard-settings .figure {
|
|
||||||
float: right;
|
|
||||||
max-width: 30%;
|
|
||||||
margin: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#keyboard-settings .figure img {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#zoom-settings {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#zoom-out, #zoom-in, #zoom-state {
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
#zoom-out, #zoom-in {
|
|
||||||
max-width: 3em;
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.5);
|
|
||||||
background: rgba(0, 0, 0, 0.1);
|
|
||||||
border-radius: 2em;
|
|
||||||
margin: 0.5em;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
#zoom-out img, #zoom-in img {
|
|
||||||
max-width: 100%;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
#zoom-out:hover, #zoom-in:hover {
|
|
||||||
border: 1px solid rgba(0, 0, 0, 1);
|
|
||||||
background: #CDA;
|
|
||||||
}
|
|
||||||
|
|
||||||
#zoom-out:hover img, #zoom-in:hover img {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
#zoom-state {
|
|
||||||
font-size: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#text-input {
|
|
||||||
display: none;
|
|
||||||
position: absolute;
|
|
||||||
|
|
||||||
border-top: 1px solid rgba(0, 0, 0, 0.5);
|
|
||||||
background: #CDA;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#text-input-field,
|
|
||||||
#text-input-buttons {
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
#text-input-field {
|
|
||||||
width: 30%;
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
#text-input-buttons {
|
|
||||||
width: 70%;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
#target {
|
|
||||||
|
|
||||||
border: none;
|
|
||||||
border-radius: 0;
|
|
||||||
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
font-size: 12pt;
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
resize: none;
|
|
||||||
outline: none;
|
|
||||||
|
|
||||||
margin: 0;
|
|
||||||
padding: 0.25em;
|
|
||||||
padding-left: 0;
|
|
||||||
background: #CDA;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#text-input.open {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
#sent-history {
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
padding: 0.25em;
|
|
||||||
padding-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#sent-history .sent-text {
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: baseline;
|
|
||||||
white-space: pre;
|
|
||||||
font-size: 12pt;
|
|
||||||
|
|
||||||
animation: fadeout 1s linear;
|
|
||||||
-webkit-animation: fadeout 1s linear;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#text-input-buttons button {
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.5);
|
|
||||||
background: none;
|
|
||||||
color: black;
|
|
||||||
box-shadow: none;
|
|
||||||
text-shadow: none;
|
|
||||||
padding: 0.25em;
|
|
||||||
max-width: 20%;
|
|
||||||
margin: 0.1em;
|
|
||||||
min-width: 3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#text-input-buttons button:active,
|
|
||||||
#text-input-buttons button.pressed {
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.5);
|
|
||||||
background: rgba(0, 0, 0, 0.75);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification.message {
|
|
||||||
background: #DFD;
|
|
||||||
animation: fadein 0.125s linear, fadeout 2s 3s linear;
|
|
||||||
-webkit-animation: fadein 0.125s linear, fadeout 2s 3s linear;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification.message .caption {
|
|
||||||
vertical-align: middle;
|
|
||||||
white-space: normal;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
@ -1,153 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 Glyptodon, Inc.
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
* THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
.keyboard-container {
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
position: fixed;
|
|
||||||
left: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: 100%;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
border-top: 1px solid black;
|
|
||||||
background: #222;
|
|
||||||
opacity: 0.85;
|
|
||||||
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.guac-keyboard {
|
|
||||||
display: inline-block;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
cursor: default;
|
|
||||||
|
|
||||||
text-align: left;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.guac-keyboard .guac-keyboard-key-container {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.guac-keyboard .guac-keyboard-key {
|
|
||||||
background: #444;
|
|
||||||
border: 1px outset #888;
|
|
||||||
-moz-border-radius: 0.1em;
|
|
||||||
-webkit-border-radius: 0.1em;
|
|
||||||
-khtml-border-radius: 0.1em;
|
|
||||||
border-radius: 0.1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.guac-keyboard .guac-keyboard-cap {
|
|
||||||
color: white;
|
|
||||||
font-family: sans-serif;
|
|
||||||
font-size: 50%;
|
|
||||||
font-weight: lighter;
|
|
||||||
text-align: center;
|
|
||||||
white-space: pre;
|
|
||||||
}
|
|
||||||
|
|
||||||
.guac-keyboard .guac-keyboard-key:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.guac-keyboard .guac-keyboard-key.highlight {
|
|
||||||
background: #666;
|
|
||||||
border-color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.guac-keyboard.guac-keyboard-modifier-shift .guac-keyboard-key.shift,
|
|
||||||
.guac-keyboard.guac-keyboard-modifier-numsym .guac-keyboard-key.numsym {
|
|
||||||
background: #882;
|
|
||||||
border-color: #DD4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.guac-keyboard.guac-keyboard-modifier-control .guac-keyboard-key.control,
|
|
||||||
.guac-keyboard.guac-keyboard-modifier-numsym .guac-keyboard-key.numsym {
|
|
||||||
background: #882;
|
|
||||||
border-color: #DD4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.guac-keyboard.guac-keyboard-modifier-alt .guac-keyboard-key.alt,
|
|
||||||
.guac-keyboard.guac-keyboard-modifier-numsym .guac-keyboard-key.numsym {
|
|
||||||
background: #882;
|
|
||||||
border-color: #DD4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.guac-keyboard.guac-keyboard-modifier-super .guac-keyboard-key.super,
|
|
||||||
.guac-keyboard.guac-keyboard-modifier-numsym .guac-keyboard-key.numsym {
|
|
||||||
background: #882;
|
|
||||||
border-color: #DD4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.guac-keyboard .guac-keyboard-key.guac-keyboard-pressed {
|
|
||||||
background: #822;
|
|
||||||
border-color: #D44;
|
|
||||||
border-style: inset;
|
|
||||||
}
|
|
||||||
|
|
||||||
.guac-keyboard .guac-keyboard-row {
|
|
||||||
line-height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.guac-keyboard .guac-keyboard-column {
|
|
||||||
display: inline-block;
|
|
||||||
text-align: center;
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
|
|
||||||
.guac-keyboard .guac-keyboard-gap {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Hide keycaps requiring modifiers which are NOT currently active. */
|
|
||||||
.guac-keyboard:not(.guac-keyboard-modifier-caps)
|
|
||||||
.guac-keyboard-cap.guac-keyboard-requires-caps,
|
|
||||||
|
|
||||||
.guac-keyboard:not(.guac-keyboard-modifier-numsym)
|
|
||||||
.guac-keyboard-cap.guac-keyboard-requires-numsym,
|
|
||||||
|
|
||||||
.guac-keyboard:not(.guac-keyboard-modifier-shift)
|
|
||||||
.guac-keyboard-cap.guac-keyboard-requires-shift,
|
|
||||||
|
|
||||||
/* Hide keycaps NOT requiring modifiers which ARE currently active, where that
|
|
||||||
modifier is used to determine which cap is displayed for the current key. */
|
|
||||||
.guac-keyboard.guac-keyboard-modifier-shift
|
|
||||||
.guac-keyboard-key.guac-keyboard-uses-shift
|
|
||||||
.guac-keyboard-cap:not(.guac-keyboard-requires-shift),
|
|
||||||
|
|
||||||
.guac-keyboard.guac-keyboard-modifier-numsym
|
|
||||||
.guac-keyboard-key.guac-keyboard-uses-numsym
|
|
||||||
.guac-keyboard-cap:not(.guac-keyboard-requires-numsym),
|
|
||||||
|
|
||||||
.guac-keyboard.guac-keyboard-modifier-caps
|
|
||||||
.guac-keyboard-key.guac-keyboard-uses-caps
|
|
||||||
.guac-keyboard-cap:not(.guac-keyboard-requires-caps) {
|
|
||||||
|
|
||||||
display: none;
|
|
||||||
|
|
||||||
}
|
|
@ -1,626 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 Glyptodon, Inc.
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
* THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
@import url('animation.css');
|
|
||||||
|
|
||||||
* {
|
|
||||||
-webkit-tap-highlight-color: rgba(0,0,0,0);
|
|
||||||
-webkit-box-sizing: border-box;
|
|
||||||
-moz-box-sizing: border-box;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=checkbox], input[type=number], input[type=text], input[type=radio], label, textarea {
|
|
||||||
-webkit-tap-highlight-color: rgba(128,192,128,0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=submit], button {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.location, input[type=text], input[type=number], input[type=password], textarea {
|
|
||||||
border: 1px solid #777;
|
|
||||||
-moz-border-radius: 0.2em;
|
|
||||||
-webkit-border-radius: 0.2em;
|
|
||||||
-khtml-border-radius: 0.2em;
|
|
||||||
border-radius: 0.2em;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 16em;
|
|
||||||
padding: 0.25em;
|
|
||||||
font-size: 10pt;
|
|
||||||
background: white;
|
|
||||||
cursor: text;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea {
|
|
||||||
max-width: none;
|
|
||||||
width: 30em;
|
|
||||||
height: 10em;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="submit"], button {
|
|
||||||
|
|
||||||
background-color: #3C3C3C;
|
|
||||||
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.4);
|
|
||||||
-moz-border-radius: 0.25em;
|
|
||||||
-webkit-border-radius: 0.25em;
|
|
||||||
-khtml-border-radius: 0.25em;
|
|
||||||
border-radius: 0.25em;
|
|
||||||
|
|
||||||
color: white;
|
|
||||||
text-shadow: -1px -1px rgba(0, 0, 0, 0.3);
|
|
||||||
font-size: 1em;
|
|
||||||
|
|
||||||
box-shadow: inset -1px -1px 0.1em rgba(0, 0, 0, 0.25),
|
|
||||||
inset 1px 1px 0.1em rgba(255, 255, 255, 0.25),
|
|
||||||
-1px -1px 0.1em rgba(0, 0, 0, 0.25),
|
|
||||||
1px 1px 0.1em rgba(255, 255, 255, 0.25);
|
|
||||||
|
|
||||||
padding: 0.35em;
|
|
||||||
padding-right: 1em;
|
|
||||||
padding-left: 1em;
|
|
||||||
min-width: 5em;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="submit"]:hover, button:hover {
|
|
||||||
background-color: #5A5A5A;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="submit"]:active, button:active {
|
|
||||||
|
|
||||||
background-color: #2C2C2C;
|
|
||||||
|
|
||||||
box-shadow:
|
|
||||||
inset 1px 1px 0.25em rgba(0, 0, 0, 0.25),
|
|
||||||
-1px -1px 0.25em rgba(0, 0, 0, 0.25),
|
|
||||||
1px 1px 0.25em rgba(255, 255, 255, 0.25);
|
|
||||||
}
|
|
||||||
|
|
||||||
button.danger {
|
|
||||||
background: #A43;
|
|
||||||
}
|
|
||||||
|
|
||||||
button.danger:hover {
|
|
||||||
background: #C54;
|
|
||||||
}
|
|
||||||
|
|
||||||
button.danger:active {
|
|
||||||
background: #932;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
background: #EEE;
|
|
||||||
font-family: FreeSans, Helvetica, Arial, sans-serif;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
border: none;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
|
|
||||||
margin: 0;
|
|
||||||
padding: 0.5em;
|
|
||||||
|
|
||||||
font-size: 2em;
|
|
||||||
vertical-align: middle;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
|
|
||||||
border-top: 1px solid #AAA;
|
|
||||||
border-bottom: 1px solid #AAA;
|
|
||||||
background: rgba(0, 0, 0, 0.07);
|
|
||||||
|
|
||||||
padding: 0.5em;
|
|
||||||
margin: 0;
|
|
||||||
font-size: 1.5em;
|
|
||||||
|
|
||||||
font-weight: lighter;
|
|
||||||
text-shadow: 1px 1px white;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
div.section {
|
|
||||||
margin: 0;
|
|
||||||
padding: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Dialogs
|
|
||||||
*/
|
|
||||||
|
|
||||||
.dialog-container {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
bottom: 0;
|
|
||||||
right: 0;
|
|
||||||
background: rgba(0, 0, 0, 0.5);
|
|
||||||
padding: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog {
|
|
||||||
|
|
||||||
max-width: 100%;
|
|
||||||
width: 8in;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
max-height: 100%;
|
|
||||||
overflow: auto;
|
|
||||||
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.5);
|
|
||||||
background: #E7E7E7;
|
|
||||||
|
|
||||||
-moz-border-radius: 0.2em;
|
|
||||||
-webkit-border-radius: 0.2em;
|
|
||||||
-khtml-border-radius: 0.2em;
|
|
||||||
border-radius: 0.2em;
|
|
||||||
|
|
||||||
box-shadow: 0.1em 0.1em 0.2em rgba(0, 0, 0, 0.6);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog > * {
|
|
||||||
margin: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog .header {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog td {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog .overlay {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
bottom: 0;
|
|
||||||
right: 0;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog .dropdown {
|
|
||||||
|
|
||||||
position: absolute;
|
|
||||||
z-index: 2;
|
|
||||||
margin-top: -1px;
|
|
||||||
|
|
||||||
width: 3in;
|
|
||||||
max-height: 2in;
|
|
||||||
overflow: auto;
|
|
||||||
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.5);
|
|
||||||
background: white;
|
|
||||||
|
|
||||||
font-size: 10pt;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog .footer {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* List elements
|
|
||||||
*/
|
|
||||||
|
|
||||||
.list-item {
|
|
||||||
|
|
||||||
display: block;
|
|
||||||
text-align: left;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
background-size: 16px 16px;
|
|
||||||
-moz-background-size: 16px 16px;
|
|
||||||
-webkit-background-size: 16px 16px;
|
|
||||||
-khtml-background-size: 16px 16px;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center center;
|
|
||||||
opacity: 0.5;
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-item * {
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-item .caption {
|
|
||||||
padding: 0.1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-item .name {
|
|
||||||
color: black;
|
|
||||||
font-weight: normal;
|
|
||||||
padding: 0.1em;
|
|
||||||
margin-left: 0.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-item .usage {
|
|
||||||
float: right;
|
|
||||||
font-style: italic;
|
|
||||||
color: gray;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-item.in-use {
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.choice .list-item .usage {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.choice .list-item.in-use {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* List element styling
|
|
||||||
*/
|
|
||||||
|
|
||||||
.list-item.selected {
|
|
||||||
background: #DEB;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-item.selected > .icon {
|
|
||||||
opacity: 1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-item:not(.selected) .caption:hover {
|
|
||||||
background: #CDA;
|
|
||||||
}
|
|
||||||
|
|
||||||
.choice .list-item {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.choice input[type='checkbox'] {
|
|
||||||
vertical-align: top;
|
|
||||||
height: 24px;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.disabled .list-item:not(.selected) {
|
|
||||||
opacity: 0.25;
|
|
||||||
}
|
|
||||||
|
|
||||||
.disabled .list-item:not(.selected):hover {
|
|
||||||
background: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* List element fields (editing)
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
.form {
|
|
||||||
|
|
||||||
position: absolute;
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
z-index: 1;
|
|
||||||
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.5);
|
|
||||||
background: #E7E7E7;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0.25em;
|
|
||||||
|
|
||||||
-moz-border-radius: 0.2em;
|
|
||||||
-webkit-border-radius: 0.2em;
|
|
||||||
-khtml-border-radius: 0.2em;
|
|
||||||
border-radius: 0.2em;
|
|
||||||
|
|
||||||
box-shadow: 0.1em 0.1em 0.2em rgba(0, 0, 0, 0.6);
|
|
||||||
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
.form .fields th,
|
|
||||||
.form .permissions th {
|
|
||||||
font-weight: normal;
|
|
||||||
vertical-align: middle;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form h2 {
|
|
||||||
border-top: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form h3 {
|
|
||||||
font-size: 1em;
|
|
||||||
margin-bottom: 0.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form {
|
|
||||||
cursor: auto;
|
|
||||||
animation-name: fadein;
|
|
||||||
-webkit-animation-name: fadein;
|
|
||||||
animation-duration: 0.125s;
|
|
||||||
-webkit-animation-duration: 0.125s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.object-buttons {
|
|
||||||
|
|
||||||
text-align: right;
|
|
||||||
|
|
||||||
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
|
||||||
padding-top: 0.5em;
|
|
||||||
margin: 0.5em;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* List element icons
|
|
||||||
*/
|
|
||||||
|
|
||||||
.icon.user {
|
|
||||||
background-image: url('../images/user-icons/guac-user.png');
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon.user.add {
|
|
||||||
background-image: url('../images/action-icons/guac-user-add.png');
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon.connection {
|
|
||||||
background-image: url('../images/protocol-icons/guac-plug.png');
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon.connection.add {
|
|
||||||
background-image: url('../images/action-icons/guac-monitor-add.png');
|
|
||||||
}
|
|
||||||
|
|
||||||
.protocol {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.protocol .icon {
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
background-image: url('../images/protocol-icons/guac-plug.png');
|
|
||||||
background-size: 16px 16px;
|
|
||||||
-moz-background-size: 16px 16px;
|
|
||||||
-webkit-background-size: 16px 16px;
|
|
||||||
-khtml-background-size: 16px 16px;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center center;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.protocol .icon.ssh,
|
|
||||||
.protocol .icon.telnet {
|
|
||||||
background-image: url('../images/protocol-icons/guac-text.png');
|
|
||||||
}
|
|
||||||
|
|
||||||
.protocol .icon.vnc,
|
|
||||||
.protocol .icon.rdp {
|
|
||||||
background-image: url('../images/protocol-icons/guac-monitor.png');
|
|
||||||
}
|
|
||||||
|
|
||||||
.connection .thumbnail {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Groups
|
|
||||||
*/
|
|
||||||
|
|
||||||
.group > .children {
|
|
||||||
margin-left: 13px;
|
|
||||||
padding-left: 6px;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.group.expanded > .children {
|
|
||||||
display: block;
|
|
||||||
border-left: 1px dotted rgba(0, 0, 0, 0.25);
|
|
||||||
}
|
|
||||||
|
|
||||||
.group > .caption .icon.type {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.group.balancer > .caption .icon.type {
|
|
||||||
display: inline-block;
|
|
||||||
background-image: url('../images/protocol-icons/guac-monitor.png');
|
|
||||||
}
|
|
||||||
|
|
||||||
.group > .caption .icon.group {
|
|
||||||
opacity: 0.75;
|
|
||||||
background-image: url('../images/group-icons/guac-closed.png');
|
|
||||||
}
|
|
||||||
|
|
||||||
.group.expanded > .caption .icon.group {
|
|
||||||
background-image: url('../images/group-icons/guac-open.png');
|
|
||||||
}
|
|
||||||
|
|
||||||
.group.empty > .caption .icon.group {
|
|
||||||
opacity: 0.25;
|
|
||||||
background-image: url('../images/group-icons/guac-open.png');
|
|
||||||
}
|
|
||||||
|
|
||||||
.group.empty.balancer > .caption .icon.group {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Settings formatting
|
|
||||||
*/
|
|
||||||
|
|
||||||
.form dt,
|
|
||||||
.settings dt {
|
|
||||||
border-bottom: 1px dotted #AAA;
|
|
||||||
padding-bottom: 0.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form dd,
|
|
||||||
.settings dd {
|
|
||||||
margin: 1.5em;
|
|
||||||
margin-left: 2.5em;
|
|
||||||
font-size: 0.75em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#connections input.name,
|
|
||||||
#users input.name {
|
|
||||||
max-width: 80%;
|
|
||||||
width: 20em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#connection-list,
|
|
||||||
#user-list {
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.25);
|
|
||||||
min-height: 20em;
|
|
||||||
-moz-border-radius: 0.2em;
|
|
||||||
-webkit-border-radius: 0.2em;
|
|
||||||
-khtml-border-radius: 0.2em;
|
|
||||||
border-radius: 0.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#connections #add-connection,
|
|
||||||
#connections #add-connection-group,
|
|
||||||
#users #add-user {
|
|
||||||
font-size: 0.8em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#connection-add-form,
|
|
||||||
#user-add-form {
|
|
||||||
margin-bottom: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
body:not(.manage-connections) .require-manage-connections,
|
|
||||||
body:not(.manage-users) .require-manage-users {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
body:not(.add-connections) #add-connection,
|
|
||||||
body:not(.add-connection-groups) #add-connection-group,
|
|
||||||
body:not(.add-users) #user-add-form {
|
|
||||||
display: none;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
div#logout-panel {
|
|
||||||
padding: 0.45em;
|
|
||||||
text-align: right;
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.history th,
|
|
||||||
.history td {
|
|
||||||
padding-left: 1em;
|
|
||||||
padding-right: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.first-page,
|
|
||||||
.prev-page,
|
|
||||||
.set-page,
|
|
||||||
.next-page,
|
|
||||||
.last-page {
|
|
||||||
cursor: pointer;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.first-page.disabled,
|
|
||||||
.prev-page.disabled,
|
|
||||||
.set-page.disabled,
|
|
||||||
.next-page.disabled,
|
|
||||||
.last-page.disabled {
|
|
||||||
cursor: auto;
|
|
||||||
opacity: 0.25;
|
|
||||||
}
|
|
||||||
|
|
||||||
.set-page,
|
|
||||||
.more-pages {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 0.25em;
|
|
||||||
text-align: center;
|
|
||||||
min-width: 1.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.set-page {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.set-page.current {
|
|
||||||
cursor: auto;
|
|
||||||
text-decoration: none;
|
|
||||||
font-weight: bold;
|
|
||||||
background: rgba(0, 0, 0, 0.1);
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
||||||
-moz-border-radius: 0.2em;
|
|
||||||
-webkit-border-radius: 0.2em;
|
|
||||||
-khtml-border-radius: 0.2em;
|
|
||||||
border-radius: 0.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon.first-page {
|
|
||||||
background-image: url('../images/action-icons/guac-first-page.png');
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon.prev-page {
|
|
||||||
background-image: url('../images/action-icons/guac-prev-page.png');
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon.next-page {
|
|
||||||
background-image: url('../images/action-icons/guac-next-page.png');
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon.last-page {
|
|
||||||
background-image: url('../images/action-icons/guac-last-page.png');
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttons,
|
|
||||||
.list-pager-buttons {
|
|
||||||
text-align: center;
|
|
||||||
margin: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
button#logout {
|
|
||||||
|
|
||||||
background-image: url('../images/action-icons/guac-logout.png');
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: 1em;
|
|
||||||
background-position: 0.5em 0.45em;
|
|
||||||
|
|
||||||
padding-left: 1.8em;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|