Making ExtensionConfig an object, not an interface
This commit is contained in:
parent
5eb5eb818f
commit
3d94916790
|
@ -18,32 +18,130 @@
|
|||
|
||||
package org.eclipse.jetty.websocket.api.extensions;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jetty.websocket.api.util.QuoteUtil;
|
||||
|
||||
/**
|
||||
* Represents an Extension Configuration, as seen during the connection Handshake process.
|
||||
*/
|
||||
public interface ExtensionConfig
|
||||
public class ExtensionConfig
|
||||
{
|
||||
public String getName();
|
||||
public static ExtensionConfig parse(String parameterizedName)
|
||||
{
|
||||
Iterator<String> extListIter = QuoteUtil.splitAt(parameterizedName,";");
|
||||
String extToken = extListIter.next();
|
||||
|
||||
public int getParameter(String key, int defValue);
|
||||
ExtensionConfig ext = new ExtensionConfig(extToken);
|
||||
|
||||
public String getParameter(String key, String defValue);
|
||||
// now for parameters
|
||||
while (extListIter.hasNext())
|
||||
{
|
||||
String extParam = extListIter.next();
|
||||
Iterator<String> extParamIter = QuoteUtil.splitAt(extParam,"=");
|
||||
String key = extParamIter.next().trim();
|
||||
String value = null;
|
||||
if (extParamIter.hasNext())
|
||||
{
|
||||
value = extParamIter.next();
|
||||
}
|
||||
ext.setParameter(key,value);
|
||||
}
|
||||
|
||||
public String getParameterizedName();
|
||||
return ext;
|
||||
}
|
||||
|
||||
public Set<String> getParameterKeys();
|
||||
private final String name;
|
||||
private Map<String, String> parameters;
|
||||
|
||||
public ExtensionConfig(String name)
|
||||
{
|
||||
this.name = name;
|
||||
this.parameters = new HashMap<>();
|
||||
}
|
||||
|
||||
public String getName()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getParameter(String key, int defValue)
|
||||
{
|
||||
String val = parameters.get(key);
|
||||
if (val == null)
|
||||
{
|
||||
return defValue;
|
||||
}
|
||||
return Integer.valueOf(val);
|
||||
}
|
||||
|
||||
public String getParameter(String key, String defValue)
|
||||
{
|
||||
String val = parameters.get(key);
|
||||
if (val == null)
|
||||
{
|
||||
return defValue;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
public String getParameterizedName()
|
||||
{
|
||||
StringBuilder str = new StringBuilder();
|
||||
str.append(name);
|
||||
for (String param : parameters.keySet())
|
||||
{
|
||||
str.append(';');
|
||||
str.append(param);
|
||||
str.append('=');
|
||||
QuoteUtil.quoteIfNeeded(str,parameters.get(param),";=");
|
||||
}
|
||||
return str.toString();
|
||||
}
|
||||
|
||||
public Set<String> getParameterKeys()
|
||||
{
|
||||
return parameters.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return parameters in way similar to how {@link javax.net.websocket.extensions.Extension#getParameters()} works.
|
||||
*
|
||||
* @return the parameter map
|
||||
*/
|
||||
public Map<String, String> getParameters();
|
||||
public Map<String, String> getParameters()
|
||||
{
|
||||
return parameters;
|
||||
}
|
||||
|
||||
public void setParameter(String key, int value);
|
||||
/**
|
||||
* Initialize the parameters on this config from the other configuration.
|
||||
*
|
||||
* @param other
|
||||
* the other configuration.
|
||||
*/
|
||||
public void init(ExtensionConfig other)
|
||||
{
|
||||
this.parameters.clear();
|
||||
this.parameters.putAll(other.parameters);
|
||||
}
|
||||
|
||||
public void setParameter(String key, String value);
|
||||
public void setParameter(String key, int value)
|
||||
{
|
||||
parameters.put(key,Integer.toString(value));
|
||||
}
|
||||
|
||||
public void setParameter(String key, String value)
|
||||
{
|
||||
parameters.put(key,value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return getParameterizedName();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,451 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.websocket.api.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/**
|
||||
* Provide some consistent Http header value and Extension configuration parameter quoting support.
|
||||
* <p>
|
||||
* While QuotedStringTokenizer exists in jetty-util, and works great with http header values, using it in websocket-api is undesired.
|
||||
* <p>
|
||||
* <ul>
|
||||
* <li>Using QuotedStringTokenizer would introduce a dependency to jetty-util that would need to be exposed via the WebAppContext classloader</li>
|
||||
* <li>ABNF defined extension parameter parsing requirements of RFC-6455 (WebSocket) ABNF, is slightly different than the ABNF parsing defined in RFC-2616
|
||||
* (HTTP/1.1).</li>
|
||||
* <li>Future HTTPbis ABNF changes for parsing will impact QuotedStringTokenizer</li>
|
||||
* </ul>
|
||||
* It was decided to keep this implementation separate for the above reasons.
|
||||
*/
|
||||
public class QuoteUtil
|
||||
{
|
||||
private static class DeQuotingStringIterator implements Iterator<String>
|
||||
{
|
||||
private enum State
|
||||
{
|
||||
START,
|
||||
TOKEN,
|
||||
QUOTE_SINGLE,
|
||||
QUOTE_DOUBLE
|
||||
}
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private final String input;
|
||||
private final String delims;
|
||||
private StringBuilder token;
|
||||
private boolean hasToken = false;
|
||||
private int i = 0;
|
||||
|
||||
public DeQuotingStringIterator(String input, String delims)
|
||||
{
|
||||
this.input = input;
|
||||
this.delims = delims;
|
||||
int len = input.length();
|
||||
token = new StringBuilder(len > 1024?512:len / 2);
|
||||
}
|
||||
|
||||
private void appendToken(char c)
|
||||
{
|
||||
if (hasToken)
|
||||
{
|
||||
token.append(c);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Character.isWhitespace(c))
|
||||
{
|
||||
return; // skip whitespace at start of token.
|
||||
}
|
||||
else
|
||||
{
|
||||
token.append(c);
|
||||
hasToken = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void debug(String format, Object... args)
|
||||
{
|
||||
if (DEBUG)
|
||||
{
|
||||
System.out.printf(format,args);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext()
|
||||
{
|
||||
// already found a token
|
||||
if (hasToken)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
State state = State.START;
|
||||
boolean escape = false;
|
||||
int inputLen = input.length();
|
||||
|
||||
while (i < inputLen)
|
||||
{
|
||||
char c = input.charAt(i++);
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case START:
|
||||
{
|
||||
if (c == '\'')
|
||||
{
|
||||
state = State.QUOTE_SINGLE;
|
||||
appendToken(c);
|
||||
}
|
||||
else if (c == '\"')
|
||||
{
|
||||
state = State.QUOTE_DOUBLE;
|
||||
appendToken(c);
|
||||
}
|
||||
else
|
||||
{
|
||||
appendToken(c);
|
||||
state = State.TOKEN;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TOKEN:
|
||||
{
|
||||
if (delims.indexOf(c) >= 0)
|
||||
{
|
||||
debug("hasNext/t: %b [%s]%n",hasToken,token);
|
||||
return hasToken;
|
||||
}
|
||||
else if (c == '\'')
|
||||
{
|
||||
state = State.QUOTE_SINGLE;
|
||||
}
|
||||
else if (c == '\"')
|
||||
{
|
||||
state = State.QUOTE_DOUBLE;
|
||||
}
|
||||
appendToken(c);
|
||||
break;
|
||||
}
|
||||
case QUOTE_SINGLE:
|
||||
{
|
||||
if (escape)
|
||||
{
|
||||
escape = false;
|
||||
appendToken(c);
|
||||
}
|
||||
else if (c == '\'')
|
||||
{
|
||||
appendToken(c);
|
||||
state = State.TOKEN;
|
||||
}
|
||||
else if (c == '\\')
|
||||
{
|
||||
escape = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
appendToken(c);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case QUOTE_DOUBLE:
|
||||
{
|
||||
if (escape)
|
||||
{
|
||||
escape = false;
|
||||
appendToken(c);
|
||||
}
|
||||
else if (c == '\"')
|
||||
{
|
||||
appendToken(c);
|
||||
state = State.TOKEN;
|
||||
}
|
||||
else if (c == '\\')
|
||||
{
|
||||
escape = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
appendToken(c);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
debug("%s <%s> : [%s]%n",state,c,token);
|
||||
}
|
||||
|
||||
debug("hasNext/e: %b [%s]%n",hasToken,token);
|
||||
return hasToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String next()
|
||||
{
|
||||
if (!hasNext())
|
||||
{
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
String ret = token.toString();
|
||||
token.setLength(0);
|
||||
hasToken = false;
|
||||
return QuoteUtil.dequote(ret.trim());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove()
|
||||
{
|
||||
throw new UnsupportedOperationException("Remove not supported with this iterator");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ABNF from RFC 2616, RFC 822, and RFC 6455 specified characters requiring quoting.
|
||||
*/
|
||||
public static final String ABNF_REQUIRED_QUOTING = "\"'\\\n\r\t\f\b%+ ;=";
|
||||
|
||||
private static final char UNICODE_TAG = 0xFFFF;
|
||||
private static final char[] escapes = new char[32];
|
||||
|
||||
static
|
||||
{
|
||||
Arrays.fill(escapes,UNICODE_TAG);
|
||||
// non-unicode
|
||||
escapes['\b'] = 'b';
|
||||
escapes['\t'] = 't';
|
||||
escapes['\n'] = 'n';
|
||||
escapes['\f'] = 'f';
|
||||
escapes['\r'] = 'r';
|
||||
}
|
||||
|
||||
private static int dehex(byte b)
|
||||
{
|
||||
if ((b >= '0') && (b <= '9'))
|
||||
{
|
||||
return (byte)(b - '0');
|
||||
}
|
||||
if ((b >= 'a') && (b <= 'f'))
|
||||
{
|
||||
return (byte)((b - 'a') + 10);
|
||||
}
|
||||
if ((b >= 'A') && (b <= 'F'))
|
||||
{
|
||||
return (byte)((b - 'A') + 10);
|
||||
}
|
||||
throw new IllegalArgumentException("!hex:" + Integer.toHexString(0xff & b));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove quotes from a string, only if the input string start with and end with the same quote character.
|
||||
*
|
||||
* @param str
|
||||
* the string to remove surrounding quotes from
|
||||
* @return the de-quoted string
|
||||
*/
|
||||
public static String dequote(String str)
|
||||
{
|
||||
char start = str.charAt(0);
|
||||
if ((start == '\'') || (start == '\"'))
|
||||
{
|
||||
// possibly quoted
|
||||
char end = str.charAt(str.length() - 1);
|
||||
if (start == end)
|
||||
{
|
||||
// dequote
|
||||
return str.substring(1,str.length() - 1);
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
public static void escape(StringBuilder buf, String str)
|
||||
{
|
||||
for (char c : str.toCharArray())
|
||||
{
|
||||
if (c >= 32)
|
||||
{
|
||||
// non special character
|
||||
if ((c == '"') || (c == '\\'))
|
||||
{
|
||||
buf.append('\\');
|
||||
}
|
||||
buf.append(c);
|
||||
}
|
||||
else
|
||||
{
|
||||
// special characters, requiring escaping
|
||||
char escaped = escapes[c];
|
||||
|
||||
// is this a unicode escape?
|
||||
if (escaped == UNICODE_TAG)
|
||||
{
|
||||
buf.append("\\u00");
|
||||
if (c < 0x10)
|
||||
{
|
||||
buf.append('0');
|
||||
}
|
||||
buf.append(Integer.toString(c,16)); // hex
|
||||
}
|
||||
else
|
||||
{
|
||||
// normal escape
|
||||
buf.append('\\').append(escaped);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple quote of a string, escaping where needed.
|
||||
*
|
||||
* @param buf
|
||||
* the StringBuilder to append to
|
||||
* @param str
|
||||
* the string to quote
|
||||
*/
|
||||
public static void quote(StringBuilder buf, String str)
|
||||
{
|
||||
buf.append('"');
|
||||
escape(buf,str);
|
||||
buf.append('"');
|
||||
}
|
||||
|
||||
/**
|
||||
* Append into buf the provided string, adding quotes if needed.
|
||||
* <p>
|
||||
* Quoting is determined if any of the characters in the <code>delim</code> are found in the input <code>str</code>.
|
||||
*
|
||||
* @param buf
|
||||
* the buffer to append to
|
||||
* @param str
|
||||
* the string to possibly quote
|
||||
* @param delim
|
||||
* the delimiter characters that will trigger automatic quoting
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void quoteIfNeeded(StringBuilder buf, String str, String delim)
|
||||
{
|
||||
// check for delimiters in input string
|
||||
int len = str.length();
|
||||
int ch;
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
ch = str.codePointAt(i);
|
||||
if (delim.indexOf(ch) >= 0)
|
||||
{
|
||||
// found a delimiter codepoint. we need to quote it.
|
||||
quote(buf,str);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// no special delimiters used, no quote needed.
|
||||
buf.append(str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an iterator of the input string, breaking apart the string at the provided delimiters, removing quotes and triming the parts of the string as
|
||||
* needed.
|
||||
*
|
||||
* @param str
|
||||
* the input string to split apart
|
||||
* @param delims
|
||||
* the delimiter characters to split the string on
|
||||
* @return the iterator of the parts of the string, trimmed, with quotes around the string part removed, and unescaped
|
||||
*/
|
||||
public static Iterator<String> splitAt(String str, String delims)
|
||||
{
|
||||
return new DeQuotingStringIterator(str.trim(),delims);
|
||||
}
|
||||
|
||||
public static String unescape(String str)
|
||||
{
|
||||
if (str == null)
|
||||
{
|
||||
// nothing there
|
||||
return null;
|
||||
}
|
||||
|
||||
int len = str.length();
|
||||
if (len <= 1)
|
||||
{
|
||||
// impossible to be escaped
|
||||
return str;
|
||||
}
|
||||
|
||||
StringBuilder ret = new StringBuilder(len - 2);
|
||||
boolean escaped = false;
|
||||
char c;
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
c = str.charAt(i);
|
||||
if (escaped)
|
||||
{
|
||||
escaped = false;
|
||||
switch (c)
|
||||
{
|
||||
case 'n':
|
||||
ret.append('\n');
|
||||
break;
|
||||
case 'r':
|
||||
ret.append('\r');
|
||||
break;
|
||||
case 't':
|
||||
ret.append('\t');
|
||||
break;
|
||||
case 'f':
|
||||
ret.append('\f');
|
||||
break;
|
||||
case 'b':
|
||||
ret.append('\b');
|
||||
break;
|
||||
case '\\':
|
||||
ret.append('\\');
|
||||
break;
|
||||
case '/':
|
||||
ret.append('/');
|
||||
break;
|
||||
case '"':
|
||||
ret.append('"');
|
||||
break;
|
||||
case 'u':
|
||||
ret.append((char)((dehex((byte)str.charAt(i++)) << 24) + (dehex((byte)str.charAt(i++)) << 16) + (dehex((byte)str.charAt(i++)) << 8) + (dehex((byte)str
|
||||
.charAt(i++)))));
|
||||
break;
|
||||
default:
|
||||
ret.append(c);
|
||||
}
|
||||
}
|
||||
else if (c == '\\')
|
||||
{
|
||||
escaped = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
ret.append(c);
|
||||
}
|
||||
}
|
||||
return ret.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.websocket.api.extensions;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class ExtensionConfigTest
|
||||
{
|
||||
private void assertConfig(ExtensionConfig cfg, String expectedName, Map<String, String> expectedParams)
|
||||
{
|
||||
String prefix = "ExtensionConfig";
|
||||
Assert.assertThat(prefix + ".Name",cfg.getName(),is(expectedName));
|
||||
|
||||
prefix += ".getParameters()";
|
||||
Map<String, String> actualParams = cfg.getParameters();
|
||||
Assert.assertThat(prefix,actualParams,notNullValue());
|
||||
Assert.assertThat(prefix + ".size",actualParams.size(),is(expectedParams.size()));
|
||||
|
||||
for (String expectedKey : expectedParams.keySet())
|
||||
{
|
||||
Assert.assertThat(prefix + ".containsKey(" + expectedKey + ")",actualParams.containsKey(expectedKey),is(true));
|
||||
|
||||
String expectedValue = expectedParams.get(expectedKey);
|
||||
String actualValue = actualParams.get(expectedKey);
|
||||
|
||||
Assert.assertThat(prefix + ".containsKey(" + expectedKey + ")",actualValue,is(expectedValue));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseMuxExample()
|
||||
{
|
||||
ExtensionConfig cfg = ExtensionConfig.parse("mux; max-channels=4; flow-control");
|
||||
Map<String, String> expectedParams = new HashMap<>();
|
||||
expectedParams.put("max-channels","4");
|
||||
expectedParams.put("flow-control",null);
|
||||
assertConfig(cfg,"mux",expectedParams);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParsePerMessageCompressExample1()
|
||||
{
|
||||
ExtensionConfig cfg = ExtensionConfig.parse("permessage-compress; method=foo");
|
||||
Map<String, String> expectedParams = new HashMap<>();
|
||||
expectedParams.put("method","foo");
|
||||
assertConfig(cfg,"permessage-compress",expectedParams);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParsePerMessageCompressExample2()
|
||||
{
|
||||
ExtensionConfig cfg = ExtensionConfig.parse("permessage-compress; method=\"foo; x=10\"");
|
||||
Map<String, String> expectedParams = new HashMap<>();
|
||||
expectedParams.put("method","foo; x=10");
|
||||
assertConfig(cfg,"permessage-compress",expectedParams);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParsePerMessageCompressExample3()
|
||||
{
|
||||
ExtensionConfig cfg = ExtensionConfig.parse("permessage-compress; method=\"foo, bar\"");
|
||||
Map<String, String> expectedParams = new HashMap<>();
|
||||
expectedParams.put("method","foo, bar");
|
||||
assertConfig(cfg,"permessage-compress",expectedParams);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParsePerMessageCompressExample4()
|
||||
{
|
||||
ExtensionConfig cfg = ExtensionConfig.parse("permessage-compress; method=\"foo; use_x, foo\"");
|
||||
Map<String, String> expectedParams = new HashMap<>();
|
||||
expectedParams.put("method","foo; use_x, foo");
|
||||
assertConfig(cfg,"permessage-compress",expectedParams);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParsePerMessageCompressExample5()
|
||||
{
|
||||
ExtensionConfig cfg = ExtensionConfig.parse("permessage-compress; method=\"foo; x=\\\"Hello World\\\", bar\"");
|
||||
Map<String, String> expectedParams = new HashMap<>();
|
||||
expectedParams.put("method","foo; x=\"Hello World\", bar");
|
||||
assertConfig(cfg,"permessage-compress",expectedParams);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseSimple_BasicParameters()
|
||||
{
|
||||
ExtensionConfig cfg = ExtensionConfig.parse("bar; baz=2");
|
||||
Map<String, String> expectedParams = new HashMap<>();
|
||||
expectedParams.put("baz","2");
|
||||
assertConfig(cfg,"bar",expectedParams);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseSimple_NoParameters()
|
||||
{
|
||||
ExtensionConfig cfg = ExtensionConfig.parse("foo");
|
||||
Map<String, String> expectedParams = new HashMap<>();
|
||||
assertConfig(cfg,"foo",expectedParams);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.websocket.api.util;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Test QuoteUtil
|
||||
*/
|
||||
public class QuoteUtilTest
|
||||
{
|
||||
private void assertSplitAt(Iterator<String> iter, String... expectedParts)
|
||||
{
|
||||
int len = expectedParts.length;
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
String expected = expectedParts[i];
|
||||
Assert.assertThat("Split[" + i + "].hasNext()",iter.hasNext(),is(true));
|
||||
Assert.assertThat("Split[" + i + "].next()",iter.next(),is(expected));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSplitAt_PreserveQuoting()
|
||||
{
|
||||
Iterator<String> iter = QuoteUtil.splitAt("permessage-compress; method=\"foo, bar\"",";");
|
||||
assertSplitAt(iter,"permessage-compress","method=\"foo, bar\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSplitAt_PreserveQuotingWithNestedDelim()
|
||||
{
|
||||
Iterator<String> iter = QuoteUtil.splitAt("permessage-compress; method=\"foo; x=10\"",";");
|
||||
assertSplitAt(iter,"permessage-compress","method=\"foo; x=10\"");
|
||||
}
|
||||
|
||||
@Test(expected = NoSuchElementException.class)
|
||||
public void testSplitAtAllWhitespace()
|
||||
{
|
||||
Iterator<String> iter = QuoteUtil.splitAt(" ","=");
|
||||
Assert.assertThat("Has Next",iter.hasNext(),is(false));
|
||||
iter.next(); // should trigger NoSuchElementException
|
||||
}
|
||||
|
||||
@Test(expected = NoSuchElementException.class)
|
||||
public void testSplitAtEmpty()
|
||||
{
|
||||
Iterator<String> iter = QuoteUtil.splitAt("","=");
|
||||
Assert.assertThat("Has Next",iter.hasNext(),is(false));
|
||||
iter.next(); // should trigger NoSuchElementException
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSplitAtHelloWorld()
|
||||
{
|
||||
Iterator<String> iter = QuoteUtil.splitAt("Hello World"," =");
|
||||
assertSplitAt(iter,"Hello","World");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSplitAtKeyValue_Message()
|
||||
{
|
||||
Iterator<String> iter = QuoteUtil.splitAt("method=\"foo, bar\"","=");
|
||||
assertSplitAt(iter,"method","foo, bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSplitAtQuotedDelim()
|
||||
{
|
||||
// test that split ignores delimiters that occur within a quoted
|
||||
// part of the sequence.
|
||||
Iterator<String> iter = QuoteUtil.splitAt("A,\"B,C\",D",",");
|
||||
assertSplitAt(iter,"A","B,C","D");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSplitAtSimple()
|
||||
{
|
||||
Iterator<String> iter = QuoteUtil.splitAt("Hi","=");
|
||||
assertSplitAt(iter,"Hi");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSplitKeyValue_Quoted()
|
||||
{
|
||||
Iterator<String> iter = QuoteUtil.splitAt("Key = \"Value\"","=");
|
||||
assertSplitAt(iter,"Key","Value");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSplitKeyValue_QuotedValueList()
|
||||
{
|
||||
Iterator<String> iter = QuoteUtil.splitAt("Fruit = \"Apple, Banana, Cherry\"","=");
|
||||
assertSplitAt(iter,"Fruit","Apple, Banana, Cherry");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSplitKeyValue_QuotedWithDelim()
|
||||
{
|
||||
Iterator<String> iter = QuoteUtil.splitAt("Key = \"Option=Value\"","=");
|
||||
assertSplitAt(iter,"Key","Option=Value");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSplitKeyValue_Simple()
|
||||
{
|
||||
Iterator<String> iter = QuoteUtil.splitAt("Key=Value","=");
|
||||
assertSplitAt(iter,"Key","Value");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSplitKeyValue_WithWhitespace()
|
||||
{
|
||||
Iterator<String> iter = QuoteUtil.splitAt("Key = Value","=");
|
||||
assertSplitAt(iter,"Key","Value");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.websocket.api.util;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameters;
|
||||
|
||||
/**
|
||||
* Test QuoteUtil.quote(), and QuoteUtil.dequote()
|
||||
*/
|
||||
@RunWith(Parameterized.class)
|
||||
public class QuoteUtil_QuoteTest
|
||||
{
|
||||
@Parameters
|
||||
public static Collection<Object[]> data()
|
||||
{
|
||||
// The various quoting of a String
|
||||
List<Object[]> data = new ArrayList<>();
|
||||
|
||||
// @formatter:off
|
||||
data.add(new Object[] { "Hi", "\"Hi\"" });
|
||||
data.add(new Object[] { "Hello World", "\"Hello World\"" });
|
||||
data.add(new Object[] { "9.0.0", "\"9.0.0\"" });
|
||||
data.add(new Object[] { "Something \"Special\"",
|
||||
"\"Something \\\"Special\\\"\"" });
|
||||
data.add(new Object[] { "A Few\n\"Good\"\tMen",
|
||||
"\"A Few\\n\\\"Good\\\"\\tMen\"" });
|
||||
// @formatter:on
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private String unquoted;
|
||||
private String quoted;
|
||||
|
||||
public QuoteUtil_QuoteTest(String unquoted, String quoted)
|
||||
{
|
||||
this.unquoted = unquoted;
|
||||
this.quoted = quoted;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDequoting()
|
||||
{
|
||||
String actual = QuoteUtil.dequote(quoted);
|
||||
actual = QuoteUtil.unescape(actual);
|
||||
Assert.assertThat(actual,is(unquoted));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQuoting()
|
||||
{
|
||||
StringBuilder buf = new StringBuilder();
|
||||
QuoteUtil.quote(buf,unquoted);
|
||||
|
||||
String actual = buf.toString();
|
||||
Assert.assertThat(actual,is(quoted));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue