Convert "guacamole-tunnel" web application into an equivalent Apache Guacamole extension.

This commit is contained in:
Michael Jumper 2020-11-16 13:55:45 -08:00
parent e4345dfefa
commit a6876de0b1
97 changed files with 425 additions and 8909 deletions
.gitignore
guacamole-auth-uds
guacamole-tunnel

3
.gitignore vendored
View File

@ -32,9 +32,6 @@
/client/administration/installer/UDSAdminInstaller/MSChart.exe
/client/administration/installer/UDSAdminInstaller/UDSAdminSetup.exe
# /guacamole-tunnel/
/guacamole-tunnel/target
# /linuxActor/
/linuxActor/udsactor_*

View File

@ -2,8 +2,6 @@
*~
# Generated files
src/main/webapp/META-INF/
src/main/webapp/generated/
target/
# IDE-specific configuration

View 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>

View File

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

View File

@ -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()
));
}
}

View File

@ -28,9 +28,10 @@
package org.openuds.guacamole;
import com.google.inject.servlet.ServletModule;
import com.sun.jersey.guice.spi.container.servlet.GuiceContainer;
import org.apache.guacamole.tunnel.TunnelRequestService;
import com.google.inject.AbstractModule;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.environment.Environment;
import org.apache.guacamole.environment.LocalEnvironment;
import org.openuds.guacamole.config.ConfigurationService;
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
* 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
protected void configureServlets() {
protected void configure() {
// Serve servlets, etc. with Guice
bind(GuiceContainer.class);
// Bind instance of Guacamole server environment
bind(Environment.class).toInstance(environment);
// Bind UDS-specific services
bind(ConfigurationService.class);
bind(ConnectionService.class);
bind(TunnelRequestService.class);
}

View File

@ -35,16 +35,16 @@ import java.io.FileReader;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Properties;
import javax.servlet.ServletContext;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleServerException;
import org.apache.guacamole.environment.Environment;
import org.apache.guacamole.properties.StringGuacamoleProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Service that provides access to configuration information stored within
* OpenUDS' tunnel.properties file.
* Service that provides access to OpenUDS-specific configuration information
* stored within guacamole.properties.
*/
@Singleton
public class ConfigurationService {
@ -59,7 +59,14 @@ public class ConfigurationService {
* whose content dictates the base URL of the service providing connection
* 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
@ -69,9 +76,10 @@ public class ConfigurationService {
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
@ -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
* to this Guacamole integration are hosted beneath this base URI.
@ -221,11 +179,11 @@ public class ConfigurationService {
*/
public URI getUDSBaseURI() throws GuacamoleException {
// Explicitly fail if the configuration was not successfully read
if (udsBaseURI == null)
throw new GuacamoleServerException("The UDS base URI is not defined.");
// Parse URI from the UDS file (if defined)
String udsFile = environment.getRequiredProperty(UDSFILE_PROPERTY);
return udsBaseURI;
// Attempt to parse base URI from the UDS file
return getBaseURI(readServiceURI(udsFile));
}

View 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"
]
}

View File

@ -0,0 +1,7 @@
{
"DATA_SOURCE_UDS" : {
"NAME" : "UDS"
}
}

View File

@ -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/).

View File

@ -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>

View File

@ -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();
}

View File

@ -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.");
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

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

View File

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

View File

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

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

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

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

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

View File

@ -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);
}
}
}

View File

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

View File

@ -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()));
}
}

View File

@ -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));
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

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

View File

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

View File

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

View File

@ -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);
};
}

View File

@ -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);
}
}

View File

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

View File

@ -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()
);
}
}

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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">&amp;</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">&lt;</cap>
<cap if="shift">Z</cap>
<cap if="numsym,shift">z</cap>
</key>
<gap size="0.1"/>
<key>
<cap>x</cap>
<cap if="numsym">&gt;</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>

View File

@ -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">&amp;</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">&lt;</cap>
</key>
<gap size="0.1"/>
<key>
<cap>.</cap>
<cap if="shift">&gt;</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">&#x2191;</cap>
</key>
</row>
<row><gap size="0.1"/></row>
<row>
<key>
<cap keysym="0xFF51">&#x2190;</cap>
</key>
<gap size="0.1"/>
<key>
<cap keysym="0xFF54">&#x2193;</cap>
</key>
<gap size="0.1"/>
<key>
<cap keysym="0xFF53">&#x2192;</cap>
</key>
</row>
</column>
</keyboard>

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,30 +0,0 @@
This software is licensed under the MIT/X11 license.
MIT/X11 license
---------------
Copyright &copy; 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

View File

@ -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));

View File

@ -1,30 +0,0 @@
This software is licensed under the MIT/X11 license.
MIT/X11 license
---------------
Copyright &copy; 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

View File

@ -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));

View File

@ -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);
};
})();

View File

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

View File

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

View File

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

View File

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