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 * it will be assumed that it operates at this version and certain features
* may be unavailable. * may be unavailable.
*/ */
private GuacamoleProtocolVersion protocol = private GuacamoleProtocolVersion protocolVersion =
GuacamoleProtocolVersion.VERSION_1_0_0; GuacamoleProtocolVersion.VERSION_1_0_0;
/** /**
@@ -151,11 +151,21 @@ public class ConfiguredGuacamoleSocket implements GuacamoleSocket {
// Retrieve argument name // Retrieve argument name
String arg_name = arg_names.get(i); String arg_name = arg_names.get(i);
// Check for protocol version as first argument // Check for valid protocol version as first argument
if (i == 0 && arg_name.startsWith("VERSION_")) { if (i == 0) {
protocol = GuacamoleProtocolVersion.getVersion(arg_name); GuacamoleProtocolVersion version = GuacamoleProtocolVersion.parseVersion(arg_name);
arg_values[i] = protocol.toString(); if (version != null) {
continue;
// 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 // Get defined value for name
@@ -200,17 +210,11 @@ public class ConfiguredGuacamoleSocket implements GuacamoleSocket {
info.getImageMimetypes().toArray(new String[0]) info.getImageMimetypes().toArray(new String[0])
)); ));
// Check for support for timezone handshake // Send client timezone, if supported and available
if (protocol.isSupported(GuacamoleProtocolCapability.TIMEZONE_HANDSHAKE)) { if (GuacamoleProtocolCapability.TIMEZONE_HANDSHAKE.isSupported(protocolVersion)) {
// Send client timezone, if available
String timezone = info.getTimezone(); String timezone = info.getTimezone();
if (timezone != null) { if (timezone != null)
writer.writeInstruction( writer.writeInstruction(new GuacamoleInstruction("timezone", info.getTimezone()));
new GuacamoleInstruction(
"timezone",
info.getTimezone()
));
}
} }
// Send args // Send args
@@ -249,6 +253,20 @@ public class ConfiguredGuacamoleSocket implements GuacamoleSocket {
return id; 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 @Override
public GuacamoleWriter getWriter() { public GuacamoleWriter getWriter() {
return socket.getWriter(); return socket.getWriter();

View File

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

View File

@@ -23,11 +23,12 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
/** /**
* An enum that defines the available Guacamole protocol versions that can be * Representation of a Guacamole protocol version. Convenience methods are
* used between guacd and clients, and provides convenience methods for parsing * provided for parsing and comparing versions, as is necessary when
* and comparing versions. * 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 * 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 * lacks support for certain protocol-related features introduced in later
* versions. * 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 * Protocol version 1.1.0, which introduces Client-Server version
* detection, arbitrary handshake instruction order, and support * detection, arbitrary handshake instruction order, and support
* for passing the client timezone to the server during the handshake. * 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 * A regular expression that matches the VERSION_X_Y_Z pattern, where
@@ -83,7 +90,7 @@ public enum GuacamoleProtocolVersion {
* @param patch * @param patch
* The integer representation of the patch version component. * 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.major = major;
this.minor = minor; this.minor = minor;
this.patch = patch; this.patch = patch;
@@ -120,65 +127,31 @@ public enum GuacamoleProtocolVersion {
} }
/** /**
* Determines whether or not this object is greater than or equal to the * Returns whether this GuacamoleProtocolVersion is at least as recent as
* the version passed in to the method. Returns a boolean true if the * (greater than or equal to) the given version.
* version is the same as or greater than the other version, otherwise *
* false.
*
* @param otherVersion * @param otherVersion
* The version to which this object should be compared. * The version to which this GuacamoleProtocolVersion should be compared.
* *
* @return * @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 is not the same, return inequality
if (major != otherVersion.getMajor()) if (major != otherVersion.getMajor())
return this.major > major; return this.major > otherVersion.getMajor();
// Major is the same, but minor is not, return minor inequality // Major is the same, but minor is not, return minor inequality
if (minor != otherVersion.getMinor()) if (minor != otherVersion.getMinor())
return this.minor > minor; return this.minor > otherVersion.getMinor();
// Major and minor are equal, so return patch inequality // Major and minor are equal, so return patch inequality
return patch >= otherVersion.getPatch(); 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 * Parse the String format of the version provided and return the
* the enum value matching that version. If no value is provided, return * 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 * 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. * 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 // Validate format of version string
if (version == null || version.isEmpty())
return null;
// Check the string against the pattern matcher
Matcher versionMatcher = VERSION_PATTERN.matcher(version); Matcher versionMatcher = VERSION_PATTERN.matcher(version);
// If there is no RegEx match, return null
if (!versionMatcher.matches()) if (!versionMatcher.matches())
return null; 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))
);
} }
/** @Override
* Returns true if the specified capability is supported in the current public int hashCode() {
* protocol version, otherwise false. int hash = 7;
* hash = 61 * hash + this.major;
* @param capability hash = 61 * hash + this.minor;
* The protocol capability that is being checked for support. hash = 61 * hash + this.patch;
* return hash;
* @return }
* True if the capability is supported in the current version,
* otherwise false. @Override
*/ public boolean equals(Object obj) {
public boolean isSupported(GuacamoleProtocolCapability capability) {
if (obj == null || !(obj instanceof GuacamoleProtocolVersion))
return atLeast(capability.getVersion()); 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());
}
}