mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 13:17:41 +00:00
GUAC-340: Add TokenFilter class which performs arbitrary token replacement.
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Glyptodon LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.glyptodon.guacamole.token;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Filtering object which replaces tokens of the form "${TOKEN_NAME}" with
|
||||
* their corresponding values. Unknown tokens are not replaced. If TOKEN_NAME
|
||||
* is a valid token, the literal value "${TOKEN_NAME}" can be included by using
|
||||
* "$${TOKEN_NAME}".
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class TokenFilter {
|
||||
|
||||
/**
|
||||
* Regular expression which matches individual tokens, with additional
|
||||
* capturing groups for convenient retrieval of leading text, the possible
|
||||
* escape character preceding the token, the name of the token, and the
|
||||
* entire token itself.
|
||||
*/
|
||||
private final Pattern tokenPattern = Pattern.compile("(.*?)(^|.)(\\$\\{([A-Za-z0-9_]*)\\})");
|
||||
|
||||
/**
|
||||
* The index of the capturing group within tokenPattern which matches
|
||||
* non-token text preceding a possible token.
|
||||
*/
|
||||
private static final int LEADING_TEXT_GROUP = 1;
|
||||
|
||||
/**
|
||||
* The index of the capturing group within tokenPattern which matches the
|
||||
* character immediately preceding a possible token, possibly denoting that
|
||||
* the token should instead be interpreted as a literal.
|
||||
*/
|
||||
private static final int ESCAPE_CHAR_GROUP = 2;
|
||||
|
||||
/**
|
||||
* The index of the capturing group within tokenPattern which matches the
|
||||
* entire token, including the leading "${" and terminating "}" strings.
|
||||
*/
|
||||
private static final int TOKEN_GROUP = 3;
|
||||
|
||||
/**
|
||||
* The index of the capturing group within tokenPattern which matches only
|
||||
* the token name contained within the "${" and "}" strings.
|
||||
*/
|
||||
private static final int TOKEN_NAME_GROUP = 4;
|
||||
|
||||
/**
|
||||
* The values of all known tokens.
|
||||
*/
|
||||
private final Map<String, String> tokenValues = new HashMap<String, String>();
|
||||
|
||||
/**
|
||||
* Sets the token having the given name to the given value. Any existing
|
||||
* value for that token is replaced.
|
||||
*
|
||||
* @param name
|
||||
* The name of the token to set.
|
||||
*
|
||||
* @param value
|
||||
* The value to set the token to.
|
||||
*/
|
||||
public void setToken(String name, String value) {
|
||||
tokenValues.put(name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the token with the given name, or null if no such
|
||||
* token has been set.
|
||||
*
|
||||
* @param name
|
||||
* The name of the token to return.
|
||||
*
|
||||
* @return
|
||||
* The value of the token with the given name, or null if no such
|
||||
* token exists.
|
||||
*/
|
||||
public String getToken(String name) {
|
||||
return tokenValues.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the value of the token with the given name. If no such token
|
||||
* exists, this function has no effect.
|
||||
*
|
||||
* @param name
|
||||
* The name of the token whose value should be removed.
|
||||
*/
|
||||
public void unsetToken(String name) {
|
||||
tokenValues.remove(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of all tokens, with each key being a token name, and each
|
||||
* value being the corresponding token value. Changes to this map will
|
||||
* directly affect the tokens associated with this filter.
|
||||
*
|
||||
* @return
|
||||
* A map of all token names and their corresponding values.
|
||||
*/
|
||||
public Map<String, String> getTokens() {
|
||||
return tokenValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces all current token values with the contents of the given map,
|
||||
* where each map key represents a token name, and each map value
|
||||
* represents a token value.
|
||||
*
|
||||
* @param tokens
|
||||
* A map containing the token names and corresponding values to
|
||||
* assign.
|
||||
*/
|
||||
public void setTokens(Map<String, String> tokens) {
|
||||
tokenValues.clear();
|
||||
tokenValues.putAll(tokens);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the given string, replacing any tokens with their corresponding
|
||||
* values.
|
||||
*
|
||||
* @param input
|
||||
* The string to filter.
|
||||
*
|
||||
* @return
|
||||
* A copy of the input string, with any tokens replaced with their
|
||||
* corresponding values.
|
||||
*/
|
||||
public String filter(String input) {
|
||||
|
||||
StringBuilder output = new StringBuilder();
|
||||
Matcher tokenMatcher = tokenPattern.matcher(input);
|
||||
|
||||
// Track last regex match
|
||||
int endOfLastMatch = 0;
|
||||
|
||||
// For each possible token
|
||||
while (tokenMatcher.find()) {
|
||||
|
||||
// Pull possible leading text and first char before possible token
|
||||
String literal = tokenMatcher.group(LEADING_TEXT_GROUP);
|
||||
String escape = tokenMatcher.group(ESCAPE_CHAR_GROUP);
|
||||
|
||||
// Append leading non-token text
|
||||
output.append(literal);
|
||||
|
||||
// If char before token is '$', the token itself is escaped
|
||||
if ("$".equals(escape)) {
|
||||
String notToken = tokenMatcher.group(TOKEN_GROUP);
|
||||
output.append(notToken);
|
||||
}
|
||||
|
||||
// If char is not '$', interpret as a token
|
||||
else {
|
||||
|
||||
// The char before the token, if any, is a literal
|
||||
output.append(escape);
|
||||
|
||||
// Pull token value
|
||||
String tokenName = tokenMatcher.group(TOKEN_NAME_GROUP);
|
||||
String tokenValue = getToken(tokenName);
|
||||
|
||||
// If token is unknown, interpret as literal
|
||||
if (tokenValue == null) {
|
||||
String notToken = tokenMatcher.group(TOKEN_GROUP);
|
||||
output.append(notToken);
|
||||
}
|
||||
|
||||
// Otherwise, substitute value
|
||||
else
|
||||
output.append(tokenValue);
|
||||
|
||||
}
|
||||
|
||||
// Update last regex match
|
||||
endOfLastMatch = tokenMatcher.end();
|
||||
|
||||
}
|
||||
|
||||
// Append any remaining non-token text
|
||||
output.append(input.substring(endOfLastMatch));
|
||||
|
||||
return output.toString();
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Glyptodon LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.glyptodon.guacamole.token;
|
||||
|
||||
import org.junit.Test;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Test which verifies the filtering functionality of TokenFilter.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class TokenFilterTest {
|
||||
|
||||
/**
|
||||
* Verifies that token replacement via filter() functions as specified.
|
||||
*/
|
||||
@Test
|
||||
public void testFilter() {
|
||||
|
||||
// Create token filter
|
||||
TokenFilter tokenFilter = new TokenFilter();
|
||||
tokenFilter.setToken("TOKEN_A", "value-of-a");
|
||||
tokenFilter.setToken("TOKEN_B", "value-of-b");
|
||||
|
||||
// Test basic substitution and escaping
|
||||
assertEquals(
|
||||
"$${NOPE}hellovalue-of-aworldvalue-of-b${NOT_A_TOKEN}",
|
||||
tokenFilter.filter("$$${NOPE}hello${TOKEN_A}world${TOKEN_B}$${NOT_A_TOKEN}")
|
||||
);
|
||||
|
||||
// Unknown tokens must be interpreted as literals
|
||||
assertEquals(
|
||||
"${NOPE}hellovalue-of-aworld${TOKEN_C}",
|
||||
tokenFilter.filter("${NOPE}hello${TOKEN_A}world${TOKEN_C}")
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user