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