You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2014/06/25 17:52:29 UTC

svn commit: r1605454 - in /tomcat/trunk: java/org/apache/tomcat/websocket/ java/org/apache/tomcat/websocket/server/ webapps/docs/

Author: markt
Date: Wed Jun 25 15:52:29 2014
New Revision: 1605454

URL: http://svn.apache.org/r1605454
Log:
Handle preference selection for multiple extension headers with the same name
Tighten up parameter validation for permessage-deflate

Modified:
    tomcat/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties
    tomcat/trunk/java/org/apache/tomcat/websocket/PerMessageDeflate.java
    tomcat/trunk/java/org/apache/tomcat/websocket/TransformationFactory.java
    tomcat/trunk/java/org/apache/tomcat/websocket/server/UpgradeUtil.java
    tomcat/trunk/webapps/docs/changelog.xml

Modified: tomcat/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties?rev=1605454&r1=1605453&r2=1605454&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties (original)
+++ tomcat/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties Wed Jun 25 15:52:29 2014
@@ -29,6 +29,7 @@ asyncChannelWrapperSecure.wrongStateWrit
 backgroundProcessManager.processFailed=A background process failed
 
 perMessageDeflate.deflateFailed=Failed to decompress a compressed WebSocket frame
+perMessageDeflate.duplicateParameter=Duplicate definition of the [{0}] extension parameter
 perMessageDeflate.invalidWindowSize=An invalid windows of [{1}] size was specified for [{0}]. Valid values are whole numbers from 8 to 15 inclusive.
 perMessageDeflate.unknownParameter=An unknown extension parameter [{0}] was defined
 

Modified: tomcat/trunk/java/org/apache/tomcat/websocket/PerMessageDeflate.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/PerMessageDeflate.java?rev=1605454&r1=1605453&r2=1605454&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/websocket/PerMessageDeflate.java (original)
+++ tomcat/trunk/java/org/apache/tomcat/websocket/PerMessageDeflate.java Wed Jun 25 15:52:29 2014
@@ -41,54 +41,118 @@ public class PerMessageDeflate implement
 
     public static final String NAME = "permessage-deflate";
 
-    private boolean serverContextTakeover = true;
-    private boolean clientContextTakeover = true;
-
-    private final Inflater inflator;
+    private final boolean serverContextTakeover;
+    private final int serverMaxWindowBits;
+    private final boolean clientContextTakeover;
+    private final int clientMaxWindowBits;
+    private final Inflater inflator = new Inflater(true);
     private final ByteBuffer readBuffer = ByteBuffer.allocate(8192);
 
     private Transformation next;
     private boolean skipDecompression = false;
 
-    PerMessageDeflate(List<Parameter> params) {
+    static PerMessageDeflate negotiate(List<List<Parameter>> preferences) {
 
-        for (Parameter param : params) {
-            if (SERVER_NO_CONTEXT_TAKEOVER.equals(param.getName())) {
-                serverContextTakeover = false;
-            } else if (CLIENT_NO_CONTEXT_TAKEOVER.equals(param.getName())) {
-                clientContextTakeover = false;
-            } else if (SERVER_MAX_WINDOW_BITS.equals(param.getName())) {
-                int bits = Integer.parseInt(param.getValue());
-                if (bits < 8 || bits > 15) {
-                    throw new IllegalArgumentException(sm.getString(
-                            "perMessageDeflate.invalidWindowSize",
-                            SERVER_MAX_WINDOW_BITS, Integer.valueOf(bits)));
-                }
-                // Java SE API (as of Java 8) does not expose the API to control
-                // the Window size so decline this option by not including it in
-                // the response
-            } else if (CLIENT_MAX_WINDOW_BITS.equals(param.getName())) {
-                if (param.getValue() != null) {
-                    int bits = Integer.parseInt(param.getValue());
-                    if (bits < 8 || bits > 15) {
+
+        // Accept the first preference that the server is able to support
+        for (List<Parameter> preference : preferences) {
+            boolean ok = true;
+            boolean serverContextTakeover = true;
+            int serverMaxWindowBits = -1;
+            boolean clientContextTakeover = true;
+            int clientMaxWindowBits = -1;
+
+            for (Parameter param : preference) {
+                if (SERVER_NO_CONTEXT_TAKEOVER.equals(param.getName())) {
+                    if (serverContextTakeover) {
+                        serverContextTakeover = false;
+                    } else {
+                        // Duplicate definition
+                        throw new IllegalArgumentException(sm.getString(
+                                "perMessageDeflate.duplicateParameter",
+                                SERVER_NO_CONTEXT_TAKEOVER ));
+                    }
+                } else if (CLIENT_NO_CONTEXT_TAKEOVER.equals(param.getName())) {
+                    if (clientContextTakeover) {
+                        clientContextTakeover = false;
+                    } else {
+                        // Duplicate definition
+                        throw new IllegalArgumentException(sm.getString(
+                                "perMessageDeflate.duplicateParameter",
+                                CLIENT_NO_CONTEXT_TAKEOVER ));
+                    }
+                } else if (SERVER_MAX_WINDOW_BITS.equals(param.getName())) {
+                    if (serverMaxWindowBits == -1) {
+                        serverMaxWindowBits = Integer.parseInt(param.getValue());
+                        if (serverMaxWindowBits < 8 || serverMaxWindowBits > 15) {
+                            throw new IllegalArgumentException(sm.getString(
+                                    "perMessageDeflate.invalidWindowSize",
+                                    SERVER_MAX_WINDOW_BITS,
+                                    Integer.valueOf(serverMaxWindowBits)));
+                        }
+                        // Java SE API (as of Java 8) does not expose the API to
+                        // control the Window size. It is effectively hard-coded
+                        // to 15
+                        if (serverMaxWindowBits != 15) {
+                            ok = false;
+                            break;
+                        }
+                    } else {
+                        // Duplicate definition
+                        throw new IllegalArgumentException(sm.getString(
+                                "perMessageDeflate.duplicateParameter",
+                                SERVER_MAX_WINDOW_BITS ));
+                    }
+                } else if (CLIENT_MAX_WINDOW_BITS.equals(param.getName())) {
+                    if (clientMaxWindowBits == -1) {
+                        if (param.getValue() == null) {
+                            // Hint to server that the client supports this
+                            // option. Java SE API (as of Java 8) does not
+                            // expose the API to control the Window size. It is
+                            // effectively hard-coded to 15
+                            clientMaxWindowBits = 15;
+                        } else {
+                            clientMaxWindowBits = Integer.parseInt(param.getValue());
+                            if (clientMaxWindowBits < 8 || clientMaxWindowBits > 15) {
+                                throw new IllegalArgumentException(sm.getString(
+                                        "perMessageDeflate.invalidWindowSize",
+                                        CLIENT_MAX_WINDOW_BITS,
+                                        Integer.valueOf(clientMaxWindowBits)));
+                            }
+                        }
+                        // Not a problem is client specified a window size less
+                        // than 15 since the server will always use a larger
+                        // window it will still work.
+                    } else {
+                        // Duplicate definition
                         throw new IllegalArgumentException(sm.getString(
-                                "perMessageDeflate.invalidWindowSize",
-                                CLIENT_MAX_WINDOW_BITS, Integer.valueOf(bits)));
+                                "perMessageDeflate.duplicateParameter",
+                                CLIENT_MAX_WINDOW_BITS ));
                     }
+                } else {
+                    // Unknown parameter
+                    throw new IllegalArgumentException(sm.getString(
+                            "perMessageDeflate.unknownParameter", param.getName()));
                 }
-                // Java SE API (as of Java 8) does not expose the API to control
-                // the Window size so decline this option by not including it in
-                // the response
-            } else {
-                // Unknown parameter
-                throw new IllegalArgumentException(sm.getString(
-                        "perMessageDeflate.unknownParameter", param.getName()));
+            }
+            if (ok) {
+                return new PerMessageDeflate(serverContextTakeover, serverMaxWindowBits,
+                        clientContextTakeover, clientMaxWindowBits);
             }
         }
+        // Failed to negotiate agreeable terms
+        return null;
+    }
 
-        inflator = new Inflater(true);
+    private PerMessageDeflate(boolean serverContextTakeover, int serverMaxWindowBits,
+            boolean clientContextTakeover, int clientMaxWindowBits) {
+        this.serverContextTakeover = serverContextTakeover;
+        this.serverMaxWindowBits = serverMaxWindowBits;
+        this.clientContextTakeover = clientContextTakeover;
+        this.clientMaxWindowBits = clientMaxWindowBits;
     }
 
+
     @Override
     public TransformationResult getMoreData(byte opCode, boolean fin, int rsv, ByteBuffer dest)
             throws IOException {
@@ -184,9 +248,17 @@ public class PerMessageDeflate implement
         if (!serverContextTakeover) {
             params.add(new WsExtensionParameter(SERVER_NO_CONTEXT_TAKEOVER, null));
         }
+        if (serverMaxWindowBits != -1) {
+            params.add(new WsExtensionParameter(SERVER_MAX_WINDOW_BITS,
+                    Integer.toString(serverMaxWindowBits)));
+        }
         if (!clientContextTakeover) {
             params.add(new WsExtensionParameter(CLIENT_NO_CONTEXT_TAKEOVER, null));
         }
+        if (clientMaxWindowBits != -1) {
+            params.add(new WsExtensionParameter(CLIENT_MAX_WINDOW_BITS,
+                    Integer.toString(clientMaxWindowBits)));
+        }
 
         return result;
     }

Modified: tomcat/trunk/java/org/apache/tomcat/websocket/TransformationFactory.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/TransformationFactory.java?rev=1605454&r1=1605453&r2=1605454&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/websocket/TransformationFactory.java (original)
+++ tomcat/trunk/java/org/apache/tomcat/websocket/TransformationFactory.java Wed Jun 25 15:52:29 2014
@@ -16,6 +16,8 @@
  */
 package org.apache.tomcat.websocket;
 
+import java.util.List;
+
 import javax.websocket.Extension;
 
 public class TransformationFactory {
@@ -30,9 +32,9 @@ public class TransformationFactory {
         return factory;
     }
 
-    public Transformation create(Extension ext) {
-        if (PerMessageDeflate.NAME.equals(ext.getName())) {
-            return new PerMessageDeflate(ext.getParameters());
+    public Transformation create(String name, List<List<Extension.Parameter>> preferences) {
+        if (PerMessageDeflate.NAME.equals(name)) {
+            return PerMessageDeflate.negotiate(preferences);
         }
         // TODO i18n
         throw new IllegalArgumentException("Unsupported extension");

Modified: tomcat/trunk/java/org/apache/tomcat/websocket/server/UpgradeUtil.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/server/UpgradeUtil.java?rev=1605454&r1=1605453&r2=1605454&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/websocket/server/UpgradeUtil.java (original)
+++ tomcat/trunk/java/org/apache/tomcat/websocket/server/UpgradeUtil.java Wed Jun 25 15:52:29 2014
@@ -22,6 +22,7 @@ import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.util.ArrayList;
 import java.util.Enumeration;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -213,10 +214,30 @@ public class UpgradeUtil {
 
         TransformationFactory factory = TransformationFactory.getInstance();
 
+        LinkedHashMap<String,List<List<Extension.Parameter>>> extensionPreferences =
+                new LinkedHashMap<>();
+
+        // Result will likely be smaller than this
         List<Transformation> result = new ArrayList<>(negotiatedExtensions.size());
 
         for (Extension extension : negotiatedExtensions) {
-            result.add(factory.create(extension));
+            List<List<Extension.Parameter>> preferences =
+                    extensionPreferences.get(extension.getName());
+
+            if (preferences == null) {
+                preferences = new ArrayList<>();
+                extensionPreferences.put(extension.getName(), preferences);
+            }
+
+            preferences.add(extension.getParameters());
+        }
+
+        for (Map.Entry<String,List<List<Extension.Parameter>>> entry :
+            extensionPreferences.entrySet()) {
+            Transformation transformation = factory.create(entry.getKey(), entry.getValue());
+            if (transformation != null) {
+                result.add(transformation);
+            }
         }
         return result;
     }

Modified: tomcat/trunk/webapps/docs/changelog.xml
URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/changelog.xml?rev=1605454&r1=1605453&r2=1605454&view=diff
==============================================================================
--- tomcat/trunk/webapps/docs/changelog.xml (original)
+++ tomcat/trunk/webapps/docs/changelog.xml Wed Jun 25 15:52:29 2014
@@ -86,6 +86,16 @@
       </fix>
     </changelog>
   </subsection>
+  <subsection name="WebSocket">
+    <changelog>
+      <add>
+        Add support for the <code>permessage-deflate</code> extension. This is
+        currently limited to decompressing incoming messages on the server side.
+        It is expected that support will be extended to outgoing messages and to
+        the client side shortly. (markt)
+      </add>
+    </changelog>
+  </subsection>
   <subsection name="Web applications">
     <changelog>
       <fix>



---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org