diff options
7 files changed, 185 insertions, 49 deletions
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeRequest.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeRequest.java index 8729131d34..f72359982b 100644 --- a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeRequest.java +++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeRequest.java @@ -187,7 +187,7 @@ public class UpgradeRequest { return Collections.unmodifiableMap(parameters); } - + public String getProtocolVersion() { String version = getHeader("Sec-WebSocket-Version"); @@ -265,6 +265,15 @@ public class UpgradeRequest this.cookies = cookies; } + public void setExtensions(List<ExtensionConfig> configs) + { + this.extensions.clear(); + if (configs != null) + { + this.extensions.addAll(configs); + } + } + public void setHeader(String name, List<String> values) { headers.put(name,values); diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/ExtensionConfig.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/ExtensionConfig.java index 329dbea81c..d7ade452aa 100644 --- a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/ExtensionConfig.java +++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/ExtensionConfig.java @@ -18,8 +18,11 @@ package org.eclipse.jetty.websocket.api.extensions; +import java.util.ArrayList; +import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Set; @@ -30,39 +33,99 @@ import org.eclipse.jetty.websocket.api.util.QuoteUtil; */ public class ExtensionConfig { + /** + * Parse a single parameterized name. + * + * @param parameterizedName + * the parameterized name + * @return the ExtensionConfig + */ public static ExtensionConfig parse(String parameterizedName) { - Iterator<String> extListIter = QuoteUtil.splitAt(parameterizedName,";"); - String extToken = extListIter.next(); + return new ExtensionConfig(parameterizedName); + } - ExtensionConfig ext = new ExtensionConfig(extToken); + /** + * Parse enumeration of <code>Sec-WebSocket-Extensions</code> header values into a {@link ExtensionConfig} list + * + * @param valuesEnum + * the raw header values enum + * @return the list of extension configs + */ + public static List<ExtensionConfig> parseEnum(Enumeration<String> valuesEnum) + { + List<ExtensionConfig> configs = new ArrayList<>(); - // now for parameters - while (extListIter.hasNext()) + if (valuesEnum != null) { - String extParam = extListIter.next(); - Iterator<String> extParamIter = QuoteUtil.splitAt(extParam,"="); - String key = extParamIter.next().trim(); - String value = null; - if (extParamIter.hasNext()) + while (valuesEnum.hasMoreElements()) { - value = extParamIter.next(); + Iterator<String> extTokenIter = QuoteUtil.splitAt(valuesEnum.nextElement(),","); + while (extTokenIter.hasNext()) + { + String extToken = extTokenIter.next(); + configs.add(ExtensionConfig.parse(extToken)); + } } - ext.setParameter(key,value); } - return ext; + return configs; } - private final String name; - private Map<String, String> parameters; + /** + * Parse 1 or more raw <code>Sec-WebSocket-Extensions</code> header values into a {@link ExtensionConfig} list + * + * @param rawSecWebSocketExtensions + * the raw header values + * @return the list of extension configs + */ + public static List<ExtensionConfig> parseList(String... rawSecWebSocketExtensions) + { + List<ExtensionConfig> configs = new ArrayList<>(); + + for (String rawValue : rawSecWebSocketExtensions) + { + Iterator<String> extTokenIter = QuoteUtil.splitAt(rawValue,","); + while (extTokenIter.hasNext()) + { + String extToken = extTokenIter.next(); + configs.add(ExtensionConfig.parse(extToken)); + } + } + + return configs; + } - public ExtensionConfig(String name) + /** + * Convert a list of {@link ExtensionConfig} to a header value + * + * @param configs + * the list of extension configs + * @return the header value (null if no configs present) + */ + public static String toHeaderValue(List<ExtensionConfig> configs) { - this.name = name; - this.parameters = new HashMap<>(); + if ((configs == null) || (configs.isEmpty())) + { + return null; + } + StringBuilder parameters = new StringBuilder(); + boolean needsDelim = false; + for (ExtensionConfig ext : configs) + { + if (needsDelim) + { + parameters.append(", "); + } + parameters.append(ext.getParameterizedName()); + needsDelim = true; + } + return parameters.toString(); } + private final String name; + private final Map<String, String> parameters; + /** * Copy constructor */ @@ -73,12 +136,33 @@ public class ExtensionConfig this.parameters.putAll(copy.parameters); } + public ExtensionConfig(String parameterizedName) + { + Iterator<String> extListIter = QuoteUtil.splitAt(parameterizedName,";"); + this.name = extListIter.next(); + this.parameters = new HashMap<>(); + + // 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(); + } + parameters.put(key,value); + } + } + public String getName() { return name; } - public int getParameter(String key, int defValue) + public final int getParameter(String key, int defValue) { String val = parameters.get(key); if (val == null) @@ -88,7 +172,7 @@ public class ExtensionConfig return Integer.valueOf(val); } - public String getParameter(String key, String defValue) + public final String getParameter(String key, String defValue) { String val = parameters.get(key); if (val == null) @@ -98,7 +182,7 @@ public class ExtensionConfig return val; } - public String getParameterizedName() + public final String getParameterizedName() { StringBuilder str = new StringBuilder(); str.append(name); @@ -116,7 +200,7 @@ public class ExtensionConfig return str.toString(); } - public Set<String> getParameterKeys() + public final Set<String> getParameterKeys() { return parameters.keySet(); } @@ -126,7 +210,7 @@ public class ExtensionConfig * * @return the parameter map */ - public Map<String, String> getParameters() + public final Map<String, String> getParameters() { return parameters; } @@ -137,25 +221,25 @@ public class ExtensionConfig * @param other * the other configuration. */ - public void init(ExtensionConfig other) + public final void init(ExtensionConfig other) { this.parameters.clear(); this.parameters.putAll(other.parameters); } - public void setParameter(String key, int value) + public final void setParameter(String key) { - parameters.put(key,Integer.toString(value)); + parameters.put(key,null); } - public void setParameter(String key, String value) + public final void setParameter(String key, int value) { - parameters.put(key,value); + parameters.put(key,Integer.toString(value)); } - - public void setParameter(String key) + + public final void setParameter(String key, String value) { - parameters.put(key,null); + parameters.put(key,value); } @Override diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStack.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStack.java index ef39b3394d..b0344550da 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStack.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStack.java @@ -46,6 +46,7 @@ import org.eclipse.jetty.websocket.common.Parser; public class ExtensionStack extends ContainerLifeCycle implements IncomingFrames, OutgoingFrames { private static final Logger LOG = Log.getLogger(ExtensionStack.class); + private final ExtensionFactory factory; private List<Extension> extensions; private IncomingFrames nextIncoming; @@ -215,6 +216,9 @@ public class ExtensionStack extends ContainerLifeCycle implements IncomingFrames { LOG.debug("Extension Configs={}",configs); this.extensions = new ArrayList<>(); + + String rsvClaims[] = new String[3]; + for (ExtensionConfig config : configs) { Extension ext = factory.newInstance(config); @@ -223,8 +227,41 @@ public class ExtensionStack extends ContainerLifeCycle implements IncomingFrames // Extension not present on this side continue; } + + // Check RSV + if (ext.isRsv1User() && (rsvClaims[0] != null)) + { + LOG.debug("Not adding extension {}. Extension {} already claimed RSV1",config,rsvClaims[0]); + continue; + } + if (ext.isRsv2User() && (rsvClaims[1] != null)) + { + LOG.debug("Not adding extension {}. Extension {} already claimed RSV2",config,rsvClaims[1]); + continue; + } + if (ext.isRsv3User() && (rsvClaims[2] != null)) + { + LOG.debug("Not adding extension {}. Extension {} already claimed RSV3",config,rsvClaims[2]); + continue; + } + + // Add Extension extensions.add(ext); - LOG.debug("Adding Extension: {}",ext); + LOG.debug("Adding Extension: {}",config); + + // Record RSV Claims + if (ext.isRsv1User()) + { + rsvClaims[0] = ext.getName(); + } + if (ext.isRsv2User()) + { + rsvClaims[1] = ext.getName(); + } + if (ext.isRsv3User()) + { + rsvClaims[2] = ext.getName(); + } } addBean(extensions); diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStackTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStackTest.java index 0f9a6aedd9..28b2b5f257 100644 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStackTest.java +++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStackTest.java @@ -18,7 +18,7 @@ package org.eclipse.jetty.websocket.common.extensions; -import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.*; import java.util.ArrayList; import java.util.List; @@ -168,4 +168,20 @@ public class ExtensionStackTest // Shouldn't cause a NPE. LOG.debug("Shouldn't cause a NPE: {}",stack.toString()); } + + @Test + public void testNegotiateChrome32() + { + ExtensionStack stack = createExtensionStack(); + + String chromeRequest = "permessage-deflate; client_max_window_bits, x-webkit-deflate-frame"; + List<ExtensionConfig> requestedConfigs = ExtensionConfig.parseList(chromeRequest); + stack.negotiate(requestedConfigs); + + List<ExtensionConfig> negotiated = stack.getNegotiatedExtensions(); + String response = ExtensionConfig.toHeaderValue(negotiated); + + Assert.assertThat("Negotiated Extensions", response, is("permessage-deflate")); + LOG.debug("Shouldn't cause a NPE: {}",stack.toString()); + } } diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/HandshakeRFC6455.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/HandshakeRFC6455.java index aee3a73aa1..85fde179e8 100644 --- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/HandshakeRFC6455.java +++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/HandshakeRFC6455.java @@ -57,9 +57,10 @@ public class HandshakeRFC6455 implements WebSocketHandshake if (response.getExtensions() != null) { - for (ExtensionConfig ext : response.getExtensions()) + String value = ExtensionConfig.toHeaderValue(response.getExtensions()); + if (value != null) { - response.addHeader("Sec-WebSocket-Extensions",ext.getParameterizedName()); + response.addHeader("Sec-WebSocket-Extensions",value); } } diff --git a/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties b/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties index 644823c1ba..994503cc1b 100644 --- a/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties +++ b/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties @@ -3,7 +3,7 @@ org.eclipse.jetty.LEVEL=WARN # org.eclipse.jetty.io.WriteFlusher.LEVEL=DEBUG # org.eclipse.jetty.websocket.LEVEL=DEBUG -org.eclipse.jetty.websocket.LEVEL=INFO +# org.eclipse.jetty.websocket.LEVEL=INFO # org.eclipse.jetty.websocket.common.io.LEVEL=DEBUG # org.eclipse.jetty.websocket.server.ab.LEVEL=DEBUG # org.eclipse.jetty.websocket.common.Parser.LEVEL=DEBUG diff --git a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeRequest.java b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeRequest.java index 8a908a7860..fa87d1099a 100644 --- a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeRequest.java +++ b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeRequest.java @@ -28,7 +28,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; @@ -37,7 +36,6 @@ import javax.servlet.http.HttpServletRequest; import org.eclipse.jetty.websocket.api.UpgradeRequest; import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig; -import org.eclipse.jetty.websocket.api.util.QuoteUtil; import org.eclipse.jetty.websocket.api.util.WSURI; /** @@ -106,16 +104,7 @@ public class ServletUpgradeRequest extends UpgradeRequest // Parse Extension Configurations Enumeration<String> e = request.getHeaders("Sec-WebSocket-Extensions"); - while (e.hasMoreElements()) - { - Iterator<String> extTokenIter = QuoteUtil.splitAt(e.nextElement(),","); - while (extTokenIter.hasNext()) - { - String extToken = extTokenIter.next(); - ExtensionConfig config = ExtensionConfig.parse(extToken); - addExtensions(config); - } - } + setExtensions(ExtensionConfig.parseEnum(e)); } public X509Certificate[] getCertificates() |