Author: markt Date: Sun Aug 22 22:55:26 2010 New Revision: 987955 URL: http://svn.apache.org/viewvc?rev=987955&view=rev Log: Fix https://issues.apache.org/bugzilla/show_bug.cgi?id=49779 Improve handling of POST requests and FORM authentication, particularly when the user agent responds to the 302 response by repeating the POST request including a request body. Any request body provided at this point is now swallowed. Clean up the FormAuthenticator test case and extend the coverage to include bug 49779 and the remaining combinations of request methods.
Modified: tomcat/trunk/java/org/apache/catalina/authenticator/FormAuthenticator.java tomcat/trunk/test/org/apache/catalina/authenticator/TestFormAuthenticator.java tomcat/trunk/test/org/apache/catalina/startup/SimpleHttpClient.java tomcat/trunk/webapps/docs/changelog.xml Modified: tomcat/trunk/java/org/apache/catalina/authenticator/FormAuthenticator.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/authenticator/FormAuthenticator.java?rev=987955&r1=987954&r2=987955&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/authenticator/FormAuthenticator.java (original) +++ tomcat/trunk/java/org/apache/catalina/authenticator/FormAuthenticator.java Sun Aug 22 22:55:26 2010 @@ -401,7 +401,8 @@ public class FormAuthenticator * @param request The request to be restored * @param session The session containing the saved information */ - protected boolean restoreRequest(Request request, Session session) { + protected boolean restoreRequest(Request request, Session session) + throws IOException { // Retrieve and remove the SavedRequest object from our session SavedRequest saved = (SavedRequest) @@ -447,6 +448,13 @@ public class FormAuthenticator request.getCoyoteRequest().getParameters().setQueryStringEncoding( request.getConnector().getURIEncoding()); + // Swallow any request body since we will be replacing it + byte[] buffer = new byte[4096]; + InputStream is = request.getInputStream(); + while (is.read(buffer) >= 0) { + // Ignore request body + } + if ("POST".equalsIgnoreCase(saved.getMethod())) { ByteChunk body = saved.getBody(); Modified: tomcat/trunk/test/org/apache/catalina/authenticator/TestFormAuthenticator.java URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/catalina/authenticator/TestFormAuthenticator.java?rev=987955&r1=987954&r2=987955&view=diff ============================================================================== --- tomcat/trunk/test/org/apache/catalina/authenticator/TestFormAuthenticator.java (original) +++ tomcat/trunk/test/org/apache/catalina/authenticator/TestFormAuthenticator.java Sun Aug 22 22:55:26 2010 @@ -26,116 +26,155 @@ import org.apache.catalina.startup.Tomca public class TestFormAuthenticator extends TomcatBaseTest { - public void testExpect100Continue() throws Exception { - Tomcat tomcat = getTomcatInstance(); - File appDir = new File(getBuildDirectory(), "webapps/examples"); - Context ctx = - tomcat.addWebapp(null, "/examples", appDir.getAbsolutePath()); - - MapRealm realm = new MapRealm(); - realm.addUser("tomcat", "tomcat"); - realm.addUserRole("tomcat", "tomcat"); - ctx.setRealm(realm); + public void testGet() throws Exception { + doTest("GET", "GET", false); + } + + public void testPostNoContinue() throws Exception { + doTest("POST", "GET", false); + } - tomcat.start(); + public void testPostWithContinue() throws Exception { + doTest("POST", "GET", true); + } + + // Bug 49779 + public void testPostNoContinuePostRedirect() throws Exception { + doTest("POST", "POST", false); + } + + // Bug 49779 + public void testPostWithContinuePostRedirect() throws Exception { + doTest("POST", "POST", true); + } + + + private void doTest(String resourceMethod, String redirectMethod, + boolean useContinue) throws Exception { + FormAuthClient client = new FormAuthClient(); - Expect100ContinueClient client = new Expect100ContinueClient(); - client.setPort(getPort()); - // First request for authenticated resource - Exception e = client.doRequest(null); - assertNull(e); + client.setUseContinue(useContinue); + client.doResourceRequest(resourceMethod); assertTrue(client.isResponse200()); assertTrue(client.isResponseBodyOK()); - - String sessionID = client.getSessionId(); + client.reset(); // Second request for the login page - client.reset(); - e = client.doRequest(sessionID); - assertNull(e); + client.setUseContinue(useContinue); + client.doLoginRequest(); assertTrue(client.isResponse302()); assertTrue(client.isResponseBodyOK()); + client.reset(); // Third request - follow the redirect - client.reset(); - e = client.doRequest(sessionID); - assertNull(e); + client.doResourceRequest(redirectMethod); + if ("POST".equals(redirectMethod)) { + client.setUseContinue(useContinue); + } assertTrue(client.isResponse200()); assertTrue(client.isResponseBodyOK()); - - // Session ID changes after successful authentication - sessionID = client.getSessionId(); + client.reset(); // Subsequent requests - direct to the resource for (int i = 0; i < 5; i++) { - client.reset(); - e = client.doRequest(sessionID); - assertNull(e); + client.setUseContinue(useContinue); + client.doResourceRequest(resourceMethod); assertTrue(client.isResponse200()); assertTrue(client.isResponseBodyOK()); + client.reset(); } } - - private final class Expect100ContinueClient extends SimpleHttpClient { + + private final class FormAuthClient extends SimpleHttpClient { + + private static final String LOGIN_PAGE = "j_security_check"; + + private String protectedPage = "index.jsp"; + private String protectedLocation = "/examples/jsp/security/protected/"; private int requestCount = 0; + private String sessionId = null; - private Exception doRequest(String sessionId) { - try { - String request[] = new String[2]; - if (requestCount == 1) { - request[0] = - "POST /examples/jsp/security/protected/j_security_check HTTP/1.1" + CRLF; - } else if (requestCount == 2) { - request[0] = - "GET /examples/jsp/security/protected/index.jsp HTTP/1.1" + CRLF; - } else { - request[0] = - "POST /examples/jsp/security/protected/index.jsp HTTP/1.1" + CRLF; - } + private FormAuthClient() throws Exception { + Tomcat tomcat = getTomcatInstance(); + File appDir = new File(getBuildDirectory(), "webapps/examples"); + Context ctx = + tomcat.addWebapp(null, "/examples", appDir.getAbsolutePath()); + + MapRealm realm = new MapRealm(); + realm.addUser("tomcat", "tomcat"); + realm.addUserRole("tomcat", "tomcat"); + ctx.setRealm(realm); + + setPort(getPort()); + tomcat.start(); + } + + private void doResourceRequest(String method) throws Exception { + String request[] = new String[2]; + request [0] = method + " " + protectedLocation + protectedPage + + " HTTP/1.1" + CRLF; + request[0] = request[0] + + "Host: localhost" + CRLF + + "Connection: close" + CRLF; + if (getUseContinue()) { request[0] = request[0] + - "Host: localhost" + CRLF + - "Expect: 100-continue" + CRLF + - "Connection: close" + CRLF; - - if (sessionId != null) { - request[0] = request[0] + - "Cookie: JSESSIONID=" + sessionId + CRLF; - } - - if (requestCount == 1) { - request[0] = request[0] + - "Content-Type: application/x-www-form-urlencoded" + CRLF + - "Content-length: 35" + CRLF + - CRLF; - request[1] = "j_username=tomcat&j_password=tomcat"; - } else if (requestCount ==2) { - request[1] = CRLF; - } else { - request[0] = request[0] + - "Content-Type: application/x-www-form-urlencoded" + CRLF + - "Content-length: 7" + CRLF + - CRLF; - request[1] = "foo=bar"; - } - - setRequest(request); - if (requestCount != 2) { - setUseContinue(true); - } - - connect(); - processRequest(); - disconnect(); - - requestCount++; - } catch (Exception e) { - e.printStackTrace(); - return e; + "Expect: 100-continue" + CRLF; + } + if (sessionId != null) { + request[0] = request[0] + + "Cookie: JSESSIONID=" + sessionId + CRLF; } - return null; + if ("POST".equals(method)) { + request[0] = request[0] + + "Content-Type: application/x-www-form-urlencoded" + CRLF + + "Content-length: 7" + CRLF + + CRLF; + request[1] = "foo=bar"; + } else { + request[1] = CRLF; + } + doRequest(request); + } + + private void doLoginRequest() throws Exception { + String request[] = new String[2]; + request [0] = "POST " + protectedLocation + LOGIN_PAGE + + " HTTP/1.1" + CRLF; + request[0] = request[0] + + "Host: localhost" + CRLF + + "Connection: close" + CRLF; + if (getUseContinue()) { + request[0] = request[0] + + "Expect: 100-continue" + CRLF; + } + if (sessionId != null) { + request[0] = request[0] + + "Cookie: JSESSIONID=" + sessionId + CRLF; + } + request[0] = request[0] + + "Content-Type: application/x-www-form-urlencoded" + CRLF + + "Content-length: 35" + CRLF + + CRLF; + request[1] = "j_username=tomcat&j_password=tomcat"; + + doRequest(request); + } + + private void doRequest(String request[]) throws Exception { + setRequest(request); + + connect(); + processRequest(); + String newSessionId = getSessionId(); + if (newSessionId != null) { + sessionId = newSessionId; + } + disconnect(); + + requestCount++; } @Override Modified: tomcat/trunk/test/org/apache/catalina/startup/SimpleHttpClient.java URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/catalina/startup/SimpleHttpClient.java?rev=987955&r1=987954&r2=987955&view=diff ============================================================================== --- tomcat/trunk/test/org/apache/catalina/startup/SimpleHttpClient.java (original) +++ tomcat/trunk/test/org/apache/catalina/startup/SimpleHttpClient.java Sun Aug 22 22:55:26 2010 @@ -78,6 +78,10 @@ public abstract class SimpleHttpClient { useContinue = theUseContinueFlag; } + public boolean getUseContinue() { + return useContinue; + } + public void setRequestPause(int theRequestPause) { requestPause = theRequestPause; } @@ -183,6 +187,8 @@ public abstract class SimpleHttpClient { request = null; requestPause = 1000; + useContinue = false; + responseLine = null; responseHeaders = new ArrayList<String>(); responseBody = null; Modified: tomcat/trunk/webapps/docs/changelog.xml URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/changelog.xml?rev=987955&r1=987954&r2=987955&view=diff ============================================================================== --- tomcat/trunk/webapps/docs/changelog.xml (original) +++ tomcat/trunk/webapps/docs/changelog.xml Sun Aug 22 22:55:26 2010 @@ -54,6 +54,12 @@ Provide 100 Continue responses at appropriate points during FORM authentication if client indicates that they are expected. (markt) </fix> + <fix> + <bug>49779</bug>: Improve handling of POST requests and FORM + authentication, particularly when the user agent responds to the 302 + response by repeating the POST request including a request body. Any + request body provided at this point is now swallowed. (markt) + </fix> </changelog> </subsection> <subsection name="Coyote"> --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org