Author: violetagg Date: Sat Sep 13 16:08:30 2014 New Revision: 1624760 URL: http://svn.apache.org/r1624760 Log: Merged revisions 1604781, 1604788 from tomcat/trunk: - Add support for parsing the extension header - Strengthen WebSocket extension parameter validation
Modified: tomcat/tc7.0.x/trunk/ (props changed) tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/Util.java tomcat/tc7.0.x/trunk/test/org/apache/tomcat/websocket/TestUtil.java Propchange: tomcat/tc7.0.x/trunk/ ------------------------------------------------------------------------------ Merged /tomcat/trunk:r1604781,1604788 Modified: tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties?rev=1624760&r1=1624759&r2=1624760&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties Sat Sep 13 16:08:30 2014 @@ -28,6 +28,7 @@ asyncChannelWrapperSecure.wrongStateWrit backgroundProcessManager.processFailed=A background process failed +util.notToken=An illegal extension parameter was specified with name [{0}] and value [{1}] util.invalidMessageHandler=The message handler provided does not have an onMessage(Object) method util.invalidType=Unable to coerce value [{0}] to type [{1}]. That type is not supported. util.unknownDecoderType=The Decoder type [{0}] is not recognized Modified: tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/Util.java URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/Util.java?rev=1624760&r1=1624759&r2=1624760&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/Util.java (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/Util.java Sat Sep 13 16:08:30 2014 @@ -43,6 +43,7 @@ import javax.websocket.Decoder.TextStrea import javax.websocket.DeploymentException; import javax.websocket.Encoder; import javax.websocket.EndpointConfig; +import javax.websocket.Extension; import javax.websocket.MessageHandler; import javax.websocket.PongMessage; import javax.websocket.Session; @@ -352,7 +353,6 @@ public class Util { } - public static Set<MessageHandlerResult> getMessageHandlers( MessageHandler listener, EndpointConfig endpointConfig, Session session) { @@ -448,6 +448,85 @@ public class Util { } + public static void parseExtensionHeader(List<Extension> extensions, + String header) { + // The relevant ABNF for the Sec-WebSocket-Extensions is as follows: + // extension-list = 1#extension + // extension = extension-token *( ";" extension-param ) + // extension-token = registered-token + // registered-token = token + // extension-param = token [ "=" (token | quoted-string) ] + // ; When using the quoted-string syntax variant, the value + // ; after quoted-string unescaping MUST conform to the + // ; 'token' ABNF. + // + // The limiting of parameter values to tokens or "quoted tokens" makes + // the parsing of the header significantly simpler and allows a number + // of short-cuts to be taken. + + // Step one, split the header into individual extensions using ',' as a + // separator + String unparsedExtensions[] = header.split(","); + for (String unparsedExtension : unparsedExtensions) { + // Step two, split the extension into the registered name and + // parameter/value pairs using ';' as a separator + String unparsedParameters[] = unparsedExtension.split(";"); + WsExtension extension = new WsExtension(unparsedParameters[0].trim()); + + for (int i = 1; i < unparsedParameters.length; i++) { + int equalsPos = unparsedParameters[i].indexOf('='); + String name; + String value; + if (equalsPos == -1) { + name = unparsedParameters[i].trim(); + value = null; + } else { + name = unparsedParameters[i].substring(0, equalsPos).trim(); + value = unparsedParameters[i].substring(equalsPos + 1).trim(); + int len = value.length(); + if (len > 1) { + if (value.charAt(0) == '\"' && value.charAt(len - 1) == '\"') { + value = value.substring(1, value.length() - 1); + } + } + } + // Make sure value doesn't contain any of the delimiters since + // that would indicate something went wrong + if (containsDelims(name) || containsDelims(value)) { + throw new IllegalArgumentException(sm.getString( + "util.notToken", name, value)); + } + if (value != null && + (value.indexOf(',') > -1 || value.indexOf(';') > -1 || + value.indexOf('\"') > -1 || value.indexOf('=') > -1)) { + throw new IllegalArgumentException(sm.getString("", value)); + } + extension.addParameter(new WsExtensionParameter(name, value)); + } + extensions.add(extension); + } + } + + + private static boolean containsDelims(String input) { + if (input == null || input.length() == 0) { + return false; + } + for (char c : input.toCharArray()) { + switch (c) { + case ',': + case ';': + case '\"': + case '=': + return true; + default: + // NO_OP + } + + } + return false; + } + private static Method getOnMessageMethod(MessageHandler listener) { try { return listener.getClass().getMethod("onMessage", Object.class); @@ -460,6 +539,7 @@ public class Util { } } + public static class DecoderMatch { private final List<Class<? extends Decoder>> textDecoders = Modified: tomcat/tc7.0.x/trunk/test/org/apache/tomcat/websocket/TestUtil.java URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/test/org/apache/tomcat/websocket/TestUtil.java?rev=1624760&r1=1624759&r2=1624760&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/test/org/apache/tomcat/websocket/TestUtil.java (original) +++ tomcat/tc7.0.x/trunk/test/org/apache/tomcat/websocket/TestUtil.java Sat Sep 13 16:08:30 2014 @@ -16,11 +16,14 @@ */ package org.apache.tomcat.websocket; +import java.util.ArrayList; import java.util.List; import javax.websocket.EncodeException; import javax.websocket.Encoder; import javax.websocket.EndpointConfig; +import javax.websocket.Extension; +import javax.websocket.Extension.Parameter; import javax.websocket.MessageHandler; import org.junit.Assert; @@ -370,4 +373,101 @@ public class TestUtil { return null; } } + + + @Test + public void testParseExtensionHeaderSimple01() { + doTestParseExtensionHeaderSimple("ext;a=1;b=2"); + } + + @Test + public void testParseExtensionHeaderSimple02() { + doTestParseExtensionHeaderSimple("ext;a=\"1\";b=2"); + } + + @Test + public void testParseExtensionHeaderSimple03() { + doTestParseExtensionHeaderSimple("ext;a=1;b=\"2\""); + } + + @Test + public void testParseExtensionHeaderSimple04() { + doTestParseExtensionHeaderSimple(" ext ; a = 1 ; b = 2 "); + } + + private void doTestParseExtensionHeaderSimple(String header) { + // Simple test + List<Extension> result = new ArrayList<Extension>(); + Util.parseExtensionHeader(result, header); + + Assert.assertEquals(1, result.size()); + + Extension ext = result.get(0); + Assert.assertEquals("ext", ext.getName()); + List<Parameter> params = ext.getParameters(); + Assert.assertEquals(2, params.size()); + Parameter paramA = params.get(0); + Assert.assertEquals("a", paramA.getName()); + Assert.assertEquals("1", paramA.getValue()); + Parameter paramB = params.get(1); + Assert.assertEquals("b", paramB.getName()); + Assert.assertEquals("2", paramB.getValue()); + } + + + @Test + public void testParseExtensionHeaderMultiple01() { + doTestParseExtensionHeaderMultiple("ext;a=1;b=2,ext2;c;d=xyz,ext3"); + } + + @Test + public void testParseExtensionHeaderMultiple02() { + doTestParseExtensionHeaderMultiple( + " ext ; a = 1 ; b = 2 , ext2 ; c ; d = xyz , ext3 "); + } + + private void doTestParseExtensionHeaderMultiple(String header) { + // Simple test + List<Extension> result = new ArrayList<Extension>(); + Util.parseExtensionHeader(result, header); + + Assert.assertEquals(3, result.size()); + + Extension ext = result.get(0); + Assert.assertEquals("ext", ext.getName()); + List<Parameter> params = ext.getParameters(); + Assert.assertEquals(2, params.size()); + Parameter paramA = params.get(0); + Assert.assertEquals("a", paramA.getName()); + Assert.assertEquals("1", paramA.getValue()); + Parameter paramB = params.get(1); + Assert.assertEquals("b", paramB.getName()); + Assert.assertEquals("2", paramB.getValue()); + + Extension ext2 = result.get(1); + Assert.assertEquals("ext2", ext2.getName()); + List<Parameter> params2 = ext2.getParameters(); + Assert.assertEquals(2, params2.size()); + Parameter paramC = params2.get(0); + Assert.assertEquals("c", paramC.getName()); + Assert.assertNull(paramC.getValue()); + Parameter paramD = params2.get(1); + Assert.assertEquals("d", paramD.getName()); + Assert.assertEquals("xyz", paramD.getValue()); + + Extension ext3 = result.get(2); + Assert.assertEquals("ext3", ext3.getName()); + List<Parameter> params3 = ext3.getParameters(); + Assert.assertEquals(0, params3.size()); + } + + @Test(expected=IllegalArgumentException.class) + public void testParseExtensionHeaderInvalid01() { + Util.parseExtensionHeader(new ArrayList<Extension>(), "ext;a=\"1;b=2"); + } + + @Test(expected=IllegalArgumentException.class) + public void testParseExtensionHeaderInvalid02() { + Util.parseExtensionHeader(new ArrayList<Extension>(), "ext;a=1\";b=2"); + } } --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org