Author: schultz
Date: Fri Dec  3 16:07:50 2010
New Revision: 1041892

URL: http://svn.apache.org/viewvc?rev=1041892&view=rev
Log:
Fixed bug 48692: Provide option to parse application/x-www-form-urlencoded PUT 
requests

Modified:
    tomcat/trunk/java/org/apache/catalina/connector/Connector.java
    tomcat/trunk/java/org/apache/catalina/connector/Request.java
    tomcat/trunk/test/org/apache/catalina/connector/TestRequest.java
    tomcat/trunk/webapps/docs/changelog.xml
    tomcat/trunk/webapps/docs/config/ajp.xml
    tomcat/trunk/webapps/docs/config/http.xml

Modified: tomcat/trunk/java/org/apache/catalina/connector/Connector.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/connector/Connector.java?rev=1041892&r1=1041891&r2=1041892&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/connector/Connector.java (original)
+++ tomcat/trunk/java/org/apache/catalina/connector/Connector.java Fri Dec  3 
16:07:50 2010
@@ -18,7 +18,9 @@
 
 package org.apache.catalina.connector;
 
+import java.util.Arrays;
 import java.util.HashMap;
+import java.util.HashSet;
 
 import javax.management.ObjectName;
 
@@ -38,7 +40,7 @@ import org.apache.tomcat.util.res.String
 
 
 /**
- * Implementation of a Coyote connector for Tomcat 5.x.
+ * Implementation of a Coyote connector.
  *
  * @author Craig R. McClanahan
  * @author Remy Maucherat
@@ -184,6 +186,11 @@ public class Connector extends Lifecycle
     protected int maxSavePostSize = 4 * 1024;
 
 
+    protected String parseBodyMethods = "POST";
+
+    protected HashSet parseBodyMethodsSet;
+
+
     /**
      * Flag to use IP-based virtual hosting.
      */
@@ -449,6 +456,30 @@ public class Connector extends Lifecycle
     }
 
 
+    public String getParseBodyMethods()
+    {
+        return (this.parseBodyMethods);
+    }
+
+    public void setParseBodyMethods(String methods)
+    {
+        HashSet methodSet = new HashSet();
+
+        if(null != methods)
+            methodSet.addAll(Arrays.asList(methods.split("\\s*,\\s*")));
+
+        if(methodSet.contains("TRACE"))
+            throw new IllegalArgumentException("TRACE method MUST NOT include 
an entity (see RFC 2616 Section 9.6)");
+
+        this.parseBodyMethods = methods;
+        this.parseBodyMethodsSet = methodSet;
+    }
+
+    public boolean isParseBodyMethod(String method)
+    {
+        return parseBodyMethodsSet.contains(method);
+    }
+
     /**
      * Return the port number on which we listen for requests.
      */
@@ -866,6 +897,10 @@ public class Connector extends Lifecycle
         protocolHandler.setAdapter(adapter);
         protocolHandler.setDomain(getDomain());
 
+        // Make sure parseBodyMethodsSet has a default
+        if(null == parseBodyMethodsSet)
+            setParseBodyMethods(getParseBodyMethods());
+
         try {
             protocolHandler.init();
         } catch (Exception e) {

Modified: tomcat/trunk/java/org/apache/catalina/connector/Request.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/connector/Request.java?rev=1041892&r1=1041891&r2=1041892&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/connector/Request.java (original)
+++ tomcat/trunk/java/org/apache/catalina/connector/Request.java Fri Dec  3 
16:07:50 2010
@@ -2798,7 +2798,7 @@ public class Request
         if (usingInputStream || usingReader)
             return;
 
-        if (!getMethod().equalsIgnoreCase("POST"))
+        if(!getConnector().isParseBodyMethod(getMethod()))
             return;
 
         String contentType = getContentType();

Modified: tomcat/trunk/test/org/apache/catalina/connector/TestRequest.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/catalina/connector/TestRequest.java?rev=1041892&r1=1041891&r2=1041892&view=diff
==============================================================================
--- tomcat/trunk/test/org/apache/catalina/connector/TestRequest.java (original)
+++ tomcat/trunk/test/org/apache/catalina/connector/TestRequest.java Fri Dec  3 
16:07:50 2010
@@ -23,6 +23,7 @@ import java.io.PrintWriter;
 import java.net.HttpURLConnection;
 import java.net.URL;
 import java.util.Enumeration;
+import java.util.TreeMap;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
@@ -331,6 +332,198 @@ public class TestRequest extends TomcatB
         assertNotNull(is);
     }
 
+    /**
+     * Test case for https://issues.apache.org/bugzilla/show_bug.cgi?id=48692
+     * PUT requests should be able to fetch request parameters coming from
+     * the request body (when properly configured using the new parseBodyMethod
+     * setting).
+     */
+    public void testBug48692() {
+        Bug48692Client client = new Bug48692Client();
+        client.setPort(getPort());
+
+        // Make sure GET works properly
+        client.doRequest("GET", "foo=bar", null, null, false);
+
+        assertTrue("Non-200 response for GET request",
+                   client.isResponse200());
+        assertEquals("Incorrect response for GET request",
+                     "foo=bar",
+                     client.getResponseBody());
+
+        client.reset();
+
+        //
+        // Make sure POST works properly
+        //
+        // POST with separate GET and POST parameters
+        client.doRequest("POST", "foo=bar", 
"application/x-www-form-urlencoded", "bar=baz", true);
+
+        assertTrue("Non-200 response for POST request",
+                   client.isResponse200());
+        assertEquals("Incorrect response for POST request",
+                     "bar=baz,foo=bar",
+                     client.getResponseBody());
+
+        client.reset();
+
+        // POST with overlapping GET and POST parameters
+        client.doRequest("POST", "foo=bar&bar=foo", 
"application/x-www-form-urlencoded", "bar=baz&foo=baz", true);
+
+        assertTrue("Non-200 response for POST request",
+                   client.isResponse200());
+        assertEquals("Incorrect response for POST request",
+                     "bar=baz,bar=foo,foo=bar,foo=baz",
+                     client.getResponseBody());
+
+        client.reset();
+
+        // PUT without POST-style parsing
+        client.doRequest("PUT", "foo=bar&bar=foo", 
"application/x-www-form-urlencoded", "bar=baz&foo=baz", false);
+
+        assertTrue("Non-200 response for PUT/noparse request",
+                   client.isResponse200());
+        assertEquals("Incorrect response for PUT request",
+                     "bar=foo,foo=bar",
+                     client.getResponseBody());
+
+        client.reset();
+
+        // PUT with POST-style parsing
+        client.doRequest("PUT", "foo=bar&bar=foo", 
"application/x-www-form-urlencoded", "bar=baz&foo=baz", true);
+
+        assertTrue("Non-200 response for PUT request",
+                   client.isResponse200());
+        assertEquals("Incorrect response for PUT/parse request",
+                     "bar=baz,bar=foo,foo=bar,foo=baz",
+                     client.getResponseBody());
+
+        client.reset();
+
+        /*
+        private Exception doRequest(String method,
+                                    String queryString,
+                                    String contentType,
+                                    String requestBody,
+                                    boolean allowBody) {
+        */
+    }
+
+    /**
+     *
+     */
+    private static class EchoParametersServlet extends HttpServlet {
+        
+        private static final long serialVersionUID = 1L;
+
+        /**
+         * Only interested in the parameters and values for requests.
+         * Note: echos parameters in alphabetical order.
+         */
+        @Override
+        protected void service(HttpServletRequest req, HttpServletResponse 
resp)
+            throws ServletException, IOException {
+            // Just echo the parameters and values back as plain text
+            resp.setContentType("text/plain");
+            resp.setCharacterEncoding("UTF-8");
+
+            PrintWriter out = resp.getWriter();
+            
+            TreeMap<String,String[]> parameters = new 
TreeMap<String,String[]>(req.getParameterMap());
+
+            boolean first = true;
+            
+            for(String name: parameters.keySet()) {
+                String[] values = req.getParameterValues(name);
+
+                java.util.Arrays.sort(values);
+
+                for(int i=0; i<values.length; ++i)
+                {
+                    if(first)
+                        first = false;
+                    else
+                        out.print(",");
+
+                    out.print(name + "=" + values[i]);
+                }
+            }
+        }
+    }
+
+    /**
+     * Bug 48692 test client: test for allowing PUT request bodies.
+     */
+    private class Bug48692Client extends SimpleHttpClient {
+
+        private boolean init;
+        
+        private synchronized void init() throws Exception {
+            if (init) return;
+            
+            Tomcat tomcat = getTomcatInstance();
+            Context root = tomcat.addContext("", TEMP_DIR);
+            Tomcat.addServlet(root, "EchoParameters", new 
EchoParametersServlet());
+            root.addServletMapping("/echo", "EchoParameters");
+            tomcat.start();
+            
+            init = true;
+        }
+        
+        private Exception doRequest(String method,
+                                    String queryString,
+                                    String contentType,
+                                    String requestBody,
+                                    boolean allowBody) {
+            Tomcat tomcat = getTomcatInstance();
+            
+            try {
+                init();
+                if(allowBody)
+                    tomcat.getConnector().setParseBodyMethods(method);
+                else
+                    tomcat.getConnector().setParseBodyMethods(""); // never 
parse
+
+                // Open connection
+                connect();
+
+                // Re-encode the request body so that bytes = characters
+                if(null != requestBody)
+                    requestBody = new String(requestBody.getBytes("UTF-8"), 
"ASCII");
+
+                // Send specified request body using method
+                String[] request = {
+                    (
+                     method + " http://localhost:"; + getPort() + "/echo"
+                     + (null == queryString ? "" : ("?" + queryString))
+                     + " HTTP/1.1" + CRLF
+                     + "Host: localhost" + CRLF
+                     + (null == contentType ? ""
+                        : ("Content-Type: " + contentType + CRLF))
+                     + "Connection: close" + CRLF
+                     + (null == requestBody ? "" : "Content-Length: " + 
requestBody.length() + CRLF)
+                     + CRLF
+                     + (null == requestBody ? "" : requestBody)
+                     )
+                };
+
+                setRequest(request);
+                processRequest(); // blocks until response has been read
+                
+                // Close the connection
+                disconnect();
+            } catch (Exception e) {
+                return e;
+            }
+            return null;
+        }
+
+        @Override
+        public boolean isResponseBodyOK() {
+            return false; // Don't care
+        }
+    }
+
     private HttpURLConnection getConnection() throws IOException {
         final String query = "http://localhost:"; + getPort() + "/";
         URL postURL;

Modified: tomcat/trunk/webapps/docs/changelog.xml
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/changelog.xml?rev=1041892&r1=1041891&r2=1041892&view=diff
==============================================================================
--- tomcat/trunk/webapps/docs/changelog.xml (original)
+++ tomcat/trunk/webapps/docs/changelog.xml Fri Dec  3 16:07:50 2010
@@ -32,6 +32,7 @@
     <author email="kfuj...@apache.org">Keiichi Fujino</author>
     <author email="t...@apache.org">Tim Whittington</author>    
     <author email="mt...@apache.org">Mladen Turk</author>
+    <author email="schu...@apache.org">Christopher Schultz</author>
     <title>Changelog</title>
   </properties>
 
@@ -40,6 +41,10 @@
 <section name="Tomcat 7.0.6 (markt)">
   <subsection name="Catalina">
     <changelog>
+      <update>
+        <bug>48692</bug>: Provide option to parse
+        <code>application/x-www-form-urlencoded</code> PUT requests. (schultz)
+      </update>
       <fix>
         <bug>8705</bug>: <code>org.apache.catalina.SessionListener</code> now
         extends <code>java.util.EventListener</code>. (markt)

Modified: tomcat/trunk/webapps/docs/config/ajp.xml
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/config/ajp.xml?rev=1041892&r1=1041891&r2=1041892&view=diff
==============================================================================
--- tomcat/trunk/webapps/docs/config/ajp.xml (original)
+++ tomcat/trunk/webapps/docs/config/ajp.xml Fri Dec  3 16:07:50 2010
@@ -115,6 +115,18 @@
       to 4096 (4 kilobytes).</p>
     </attribute>
 
+    <attribute name="parseBodyMethods" required="false">
+      <p>A comma-separated list of HTTP methods for which request
+      bodies will be parsed for request parameters identically
+      to POST. This is useful in RESTful applications that want to
+      support POST-style semantics for PUT requests.
+      Note that any setting other than <code>POST</code> causes Tomcat
+      to behave in a way that violates the servlet specification.
+      The HTTP method TRACE is specifically forbidden here in accordance
+      with the HTTP specification.
+      The default is <code>POST</code></p>
+    </attribute>
+
     <attribute name="port" required="true">
       <p>The TCP port number on which this <strong>Connector</strong>
       will create a server socket and await incoming connections.  Your

Modified: tomcat/trunk/webapps/docs/config/http.xml
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/config/http.xml?rev=1041892&r1=1041891&r2=1041892&view=diff
==============================================================================
--- tomcat/trunk/webapps/docs/config/http.xml (original)
+++ tomcat/trunk/webapps/docs/config/http.xml Fri Dec  3 16:07:50 2010
@@ -115,6 +115,18 @@
       to 4096 (4 kilobytes).</p>
     </attribute>
 
+    <attribute name="parseBodyMethods" required="false">
+      <p>A comma-separated list of HTTP methods for which request
+      bodies will be parsed for request parameters identically
+      to POST. This is useful in RESTful applications that want to
+      support POST-style semantics for PUT requests.
+      Note that any setting other than <code>POST</code> causes Tomcat
+      to behave in a way that violates the servlet specification.
+      The HTTP method TRACE is specifically forbidden here in accordance
+      with the HTTP specification.
+      The default is <code>POST</code></p>
+    </attribute>
+
     <attribute name="port" required="true">
       <p>The TCP port number on which this <strong>Connector</strong>
       will create a server socket and await incoming connections.  Your



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

Reply via email to