Merge 1.1.0 changes back to master.

This commit is contained in:
Virtually Nick
2019-06-08 17:45:42 -04:00
4 changed files with 273 additions and 135 deletions

View File

@@ -61,7 +61,7 @@ public class ConfiguredGuacamoleSocket implements GuacamoleSocket {
* it will be assumed that it operates at this version and certain features
* may be unavailable.
*/
private GuacamoleProtocolVersion protocol =
private GuacamoleProtocolVersion protocolVersion =
GuacamoleProtocolVersion.VERSION_1_0_0;
/**
@@ -151,11 +151,21 @@ public class ConfiguredGuacamoleSocket implements GuacamoleSocket {
// Retrieve argument name
String arg_name = arg_names.get(i);
// Check for protocol version as first argument
if (i == 0 && arg_name.startsWith("VERSION_")) {
protocol = GuacamoleProtocolVersion.getVersion(arg_name);
arg_values[i] = protocol.toString();
continue;
// Check for valid protocol version as first argument
if (i == 0) {
GuacamoleProtocolVersion version = GuacamoleProtocolVersion.parseVersion(arg_name);
if (version != null) {
// Use the lowest common version supported
if (version.atLeast(GuacamoleProtocolVersion.LATEST))
version = GuacamoleProtocolVersion.LATEST;
// Respond with the version selected
arg_values[i] = version.toString();
protocolVersion = version;
continue;
}
}
// Get defined value for name
@@ -200,17 +210,11 @@ public class ConfiguredGuacamoleSocket implements GuacamoleSocket {
info.getImageMimetypes().toArray(new String[0])
));
// Check for support for timezone handshake
if (protocol.isSupported(GuacamoleProtocolCapability.TIMEZONE_HANDSHAKE)) {
// Send client timezone, if available
// Send client timezone, if supported and available
if (GuacamoleProtocolCapability.TIMEZONE_HANDSHAKE.isSupported(protocolVersion)) {
String timezone = info.getTimezone();
if (timezone != null) {
writer.writeInstruction(
new GuacamoleInstruction(
"timezone",
info.getTimezone()
));
}
if (timezone != null)
writer.writeInstruction(new GuacamoleInstruction("timezone", info.getTimezone()));
}
// Send args
@@ -249,6 +253,20 @@ public class ConfiguredGuacamoleSocket implements GuacamoleSocket {
return id;
}
/**
* Returns the version of the Guacamole protocol associated with the
* Guacamole connection negotiated by this ConfiguredGuacamoleSocket. This
* version is the lowest version common to both ConfiguredGuacamoleSocket
* and the relevant Guacamole proxy instance (guacd).
*
* @return
* The protocol version that this ConfiguredGuacamoleSocket will use to
* communicate with guacd.
*/
public GuacamoleProtocolVersion getProtocolVersion() {
return protocolVersion;
}
@Override
public GuacamoleWriter getWriter() {
return socket.getWriter();

View File

@@ -20,29 +20,33 @@
package org.apache.guacamole.protocol;
/**
* An enum that specifies protocol capabilities that can be used to help
* detect whether or not a particular protocol version contains a capability.
* Capabilities which may not be present in all versions of the Guacamole
* protocol.
*/
public enum GuacamoleProtocolCapability {
/**
* Whether or not the protocol supports arbitrary ordering of the
* handshake instructions. This was introduced in VERSION_1_1_0.
* The protocol does not require handshake instructions to be sent in a
* specific order, nor that all handshake instructions be sent. Arbitrary
* handshake order was introduced in
* {@link GuacamoleProtocolVersion#VERSION_1_1_0}.
*/
ARBITRARY_HANDSHAKE_ORDER(GuacamoleProtocolVersion.VERSION_1_1_0),
/**
* Whether or not the protocol supports the ability to dynamically
* detect the version client and server are running in order to allow
* compatibility between differing client and server versions. This
* was introduced in VERSION_1_1_0.
* Negotiation of Guacamole protocol version between client and server
* during the protocol handshake. The ability to negotiate protocol
* versions was introduced in
* {@link GuacamoleProtocolVersion#VERSION_1_1_0}.
*/
PROTOCOL_VERSION_DETECTION(GuacamoleProtocolVersion.VERSION_1_1_0),
/**
* Whether or not the protocol supports the timezone instruction during
* the Client-Server handshake phase. This was introduced in
* VERSION_1_1_0.
* Support for the "timezone" handshake instruction. The "timezone"
* instruction allows the client to request that the server forward their
* local timezone for use within the remote desktop session. Support for
* forwarding the client timezone was introduced in
* {@link GuacamoleProtocolVersion#VERSION_1_1_0}.
*/
TIMEZONE_HANDSHAKE(GuacamoleProtocolVersion.VERSION_1_1_0);
@@ -59,19 +63,23 @@ public enum GuacamoleProtocolCapability {
* The minimum required protocol version for supporting the
* capability.
*/
GuacamoleProtocolCapability(GuacamoleProtocolVersion version) {
private GuacamoleProtocolCapability(GuacamoleProtocolVersion version) {
this.version = version;
}
/**
* Returns the minimum protocol version required to support this
* capability.
* Returns whether this capability is supported in the given Guacamole
* protocol version.
*
* @param version
* The Guacamole protocol version to check.
*
* @return
* The minimum protocol version required to support this capability.
* true if this capability is supported by the given protocol version,
* false otherwise.
*/
public GuacamoleProtocolVersion getVersion() {
return version;
public boolean isSupported(GuacamoleProtocolVersion version) {
return version.atLeast(this.version);
}
}

View File

@@ -23,11 +23,12 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* An enum that defines the available Guacamole protocol versions that can be
* used between guacd and clients, and provides convenience methods for parsing
* and comparing versions.
* Representation of a Guacamole protocol version. Convenience methods are
* provided for parsing and comparing versions, as is necessary when
* determining the version of the Guacamole protocol common to guacd and a
* client.
*/
public enum GuacamoleProtocolVersion {
public class GuacamoleProtocolVersion {
/**
* Protocol version 1.0.0 and older. Any client that doesn't explicitly
@@ -36,14 +37,20 @@ public enum GuacamoleProtocolVersion {
* lacks support for certain protocol-related features introduced in later
* versions.
*/
VERSION_1_0_0(1, 0, 0),
public static final GuacamoleProtocolVersion VERSION_1_0_0 = new GuacamoleProtocolVersion(1, 0, 0);
/**
* Protocol version 1.1.0, which introduces Client-Server version
* detection, arbitrary handshake instruction order, and support
* for passing the client timezone to the server during the handshake.
*/
VERSION_1_1_0(1, 1, 0);
public static final GuacamoleProtocolVersion VERSION_1_1_0 = new GuacamoleProtocolVersion(1, 1, 0);
/**
* The most recent version of the Guacamole protocol at the time this
* version of GuacamoleProtocolVersion was built.
*/
public static final GuacamoleProtocolVersion LATEST = VERSION_1_1_0;
/**
* A regular expression that matches the VERSION_X_Y_Z pattern, where
@@ -83,7 +90,7 @@ public enum GuacamoleProtocolVersion {
* @param patch
* The integer representation of the patch version component.
*/
GuacamoleProtocolVersion(int major, int minor, int patch) {
public GuacamoleProtocolVersion(int major, int minor, int patch) {
this.major = major;
this.minor = minor;
this.patch = patch;
@@ -120,65 +127,31 @@ public enum GuacamoleProtocolVersion {
}
/**
* Determines whether or not this object is greater than or equal to the
* the version passed in to the method. Returns a boolean true if the
* version is the same as or greater than the other version, otherwise
* false.
* Returns whether this GuacamoleProtocolVersion is at least as recent as
* (greater than or equal to) the given version.
*
* @param otherVersion
* The version to which this object should be compared.
* The version to which this GuacamoleProtocolVersion should be compared.
*
* @return
* True if this object is greater than or equal to the other version.
* true if this object is at least as recent as the given version,
* false if the given version is newer.
*/
private boolean atLeast(GuacamoleProtocolVersion otherVersion) {
public boolean atLeast(GuacamoleProtocolVersion otherVersion) {
// If major is not the same, return inequality
if (major != otherVersion.getMajor())
return this.major > major;
return this.major > otherVersion.getMajor();
// Major is the same, but minor is not, return minor inequality
if (minor != otherVersion.getMinor())
return this.minor > minor;
return this.minor > otherVersion.getMinor();
// Major and minor are equal, so return patch inequality
return patch >= otherVersion.getPatch();
}
/**
* Compare this version with the major, minor, and patch components
* provided to the method, and determine if this version is compatible
* with the provided version, returning a boolean true if it is compatible,
* otherwise false. This version is compatible with the version specified
* by the provided components if the major, minor, and patch components
* are equivalent or less than those provided.
*
* @param major
* The major version component to compare for compatibility.
*
* @param minor
* The minor version component to compare for compatibility.
*
* @param patch
* The patch version component to compare for compatibility.
*
* @return
* True if this version is compatibility with the version components
* provided, otherwise false.
*/
private boolean isCompatible(int major, int minor, int patch) {
if (this.major != major)
return this.major < major;
if (this.minor != minor)
return this.minor < minor;
return this.patch <= patch;
}
/**
* Parse the String format of the version provided and return the
* the enum value matching that version. If no value is provided, return
@@ -191,60 +164,48 @@ public enum GuacamoleProtocolVersion {
* The enum value that matches the specified version, VERSION_1_0_0
* if no match is found, or null if no comparison version is provided.
*/
public static GuacamoleProtocolVersion getVersion(String version) {
public static GuacamoleProtocolVersion parseVersion(String version) {
// If nothing is passed in, return null
if (version == null || version.isEmpty())
return null;
// Check the string against the pattern matcher
// Validate format of version string
Matcher versionMatcher = VERSION_PATTERN.matcher(version);
// If there is no RegEx match, return null
if (!versionMatcher.matches())
return null;
try {
// Try the valueOf function
return valueOf(version);
}
// If nothing matches, find the closest compatible version.
catch (IllegalArgumentException e) {
int myMajor = Integer.parseInt(versionMatcher.group(1));
int myMinor = Integer.parseInt(versionMatcher.group(2));
int myPatch = Integer.parseInt(versionMatcher.group(3));
GuacamoleProtocolVersion myVersion = VERSION_1_0_0;
// Loop through possible versions, grabbing the latest compatible
for (GuacamoleProtocolVersion v : values()) {
if (v.isCompatible(myMajor, myMinor, myPatch))
myVersion = v;
}
return myVersion;
}
// Parse version number from version string
return new GuacamoleProtocolVersion(
Integer.parseInt(versionMatcher.group(1)),
Integer.parseInt(versionMatcher.group(2)),
Integer.parseInt(versionMatcher.group(3))
);
}
/**
* Returns true if the specified capability is supported in the current
* protocol version, otherwise false.
*
* @param capability
* The protocol capability that is being checked for support.
*
* @return
* True if the capability is supported in the current version,
* otherwise false.
*/
public boolean isSupported(GuacamoleProtocolCapability capability) {
@Override
public int hashCode() {
int hash = 7;
hash = 61 * hash + this.major;
hash = 61 * hash + this.minor;
hash = 61 * hash + this.patch;
return hash;
}
return atLeast(capability.getVersion());
@Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof GuacamoleProtocolVersion))
return false;
// Versions are equal if all major/minor/patch components are identical
final GuacamoleProtocolVersion otherVersion = (GuacamoleProtocolVersion) obj;
return this.major == otherVersion.getMajor()
&& this.minor == otherVersion.getMinor()
&& this.patch == otherVersion.getPatch();
}
@Override
public String toString() {
return "VERSION_" + getMajor() + "_" + getMinor() + "_" + getPatch();
}
}

View File

@@ -0,0 +1,151 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.protocol;
import org.junit.Assert;
import org.junit.Test;
/**
* Unit test for GuacamoleProtocolVersion. Verifies that Guacamole protocol
* version string parsing works as required.
*/
public class GuacamoleProtocolVersionTest {
/**
* Verifies that valid version strings are parsed successfully.
*/
@Test
public void testValidVersionParse() {
GuacamoleProtocolVersion version = GuacamoleProtocolVersion.parseVersion("VERSION_012_102_398");
Assert.assertNotNull(version);
Assert.assertEquals(12, version.getMajor());
Assert.assertEquals(102, version.getMinor());
Assert.assertEquals(398, version.getPatch());
}
/**
* Verifies that invalid version strings fail to parse.
*/
@Test
public void testInvalidVersionParse() {
Assert.assertNull(GuacamoleProtocolVersion.parseVersion("potato"));
Assert.assertNull(GuacamoleProtocolVersion.parseVersion("VERSION_"));
Assert.assertNull(GuacamoleProtocolVersion.parseVersion("VERSION___"));
Assert.assertNull(GuacamoleProtocolVersion.parseVersion("VERSION__2_3"));
Assert.assertNull(GuacamoleProtocolVersion.parseVersion("VERSION_1__3"));
Assert.assertNull(GuacamoleProtocolVersion.parseVersion("VERSION_1_2_"));
Assert.assertNull(GuacamoleProtocolVersion.parseVersion("VERSION_A_2_3"));
Assert.assertNull(GuacamoleProtocolVersion.parseVersion("VERSION_1_B_3"));
Assert.assertNull(GuacamoleProtocolVersion.parseVersion("VERSION_1_2_C"));
Assert.assertNull(GuacamoleProtocolVersion.parseVersion("_1_2_3"));
Assert.assertNull(GuacamoleProtocolVersion.parseVersion("version_1_2_3"));
}
/**
* Verifies that the atLeast() function defined by GuacamoleProtocolVersion
* behaves as required for a series of three versions which are in strictly
* increasing order (a &lt; b &lt; c).
*
* @param a
* The String representation of the version which is known to be the
* smaller than versions b and c.
*
* @param b
* The String representation of the version which is known to be
* larger than version a but smaller than version c.
*
* @param c
* The String representation of the version which is known to be the
* larger than versions a and b.
*/
private void testVersionCompare(String a, String b, String c) {
GuacamoleProtocolVersion verA = GuacamoleProtocolVersion.parseVersion(a);
GuacamoleProtocolVersion verB = GuacamoleProtocolVersion.parseVersion(b);
GuacamoleProtocolVersion verC = GuacamoleProtocolVersion.parseVersion(c);
Assert.assertTrue(verC.atLeast(verB));
Assert.assertTrue(verC.atLeast(verA));
Assert.assertTrue(verB.atLeast(verA));
Assert.assertFalse(verB.atLeast(verC));
Assert.assertFalse(verA.atLeast(verC));
Assert.assertFalse(verA.atLeast(verB));
Assert.assertTrue(verA.atLeast(verA));
Assert.assertTrue(verB.atLeast(verB));
Assert.assertTrue(verC.atLeast(verC));
}
/**
* Verifies that version order comparisons using atLeast() behave as
* required.
*/
@Test
public void testVersionCompare() {
testVersionCompare("VERSION_0_0_1", "VERSION_0_0_2", "VERSION_0_0_3");
testVersionCompare("VERSION_0_1_0", "VERSION_0_2_0", "VERSION_0_3_0");
testVersionCompare("VERSION_1_0_0", "VERSION_2_0_0", "VERSION_3_0_0");
testVersionCompare("VERSION_1_2_3", "VERSION_1_3_3", "VERSION_2_0_0");
}
/**
* Verifies that versions can be tested for equality using equals().
*/
@Test
public void testVersionEquals() {
GuacamoleProtocolVersion version;
version = GuacamoleProtocolVersion.parseVersion("VERSION_012_102_398");
Assert.assertTrue(version.equals(version));
Assert.assertTrue(version.equals(new GuacamoleProtocolVersion(12, 102, 398)));
Assert.assertFalse(version.equals(new GuacamoleProtocolVersion(12, 102, 399)));
Assert.assertFalse(version.equals(new GuacamoleProtocolVersion(12, 103, 398)));
Assert.assertFalse(version.equals(new GuacamoleProtocolVersion(11, 102, 398)));
version = GuacamoleProtocolVersion.parseVersion("VERSION_1_0_0");
Assert.assertTrue(version.equals(GuacamoleProtocolVersion.VERSION_1_0_0));
Assert.assertFalse(version.equals(GuacamoleProtocolVersion.VERSION_1_1_0));
version = GuacamoleProtocolVersion.parseVersion("VERSION_1_1_0");
Assert.assertTrue(version.equals(GuacamoleProtocolVersion.VERSION_1_1_0));
Assert.assertFalse(version.equals(GuacamoleProtocolVersion.VERSION_1_0_0));
}
/**
* Verifies that versions can be converted to their Guacamole protocol
* representation through calling toString().
*/
@Test
public void testToString() {
Assert.assertEquals("VERSION_1_0_0", GuacamoleProtocolVersion.VERSION_1_0_0.toString());
Assert.assertEquals("VERSION_1_1_0", GuacamoleProtocolVersion.VERSION_1_1_0.toString());
Assert.assertEquals("VERSION_12_103_398", new GuacamoleProtocolVersion(12, 103, 398).toString());
}
}