You are viewing a plain text version of this content. The canonical link for it is here.
Posted to scm@geronimo.apache.org by ri...@apache.org on 2008/02/21 11:46:43 UTC

svn commit: r629740 - in /geronimo/sandbox/AsyncHttpClient/src: main/java/org/apache/ahc/codec/HttpDecoder.java main/java/org/apache/ahc/codec/HttpResponseDecoder.java test/java/org/apache/ahc/ResponseHeaderParsingTest.java

Author: rickmcguire
Date: Thu Feb 21 02:46:41 2008
New Revision: 629740

URL: http://svn.apache.org/viewvc?rev=629740&view=rev
Log:
GERONIMO-3860 HttpResponseDecoder does not handle folded headers properly

Patch provided by Sangjin Lee


Added:
    geronimo/sandbox/AsyncHttpClient/src/test/java/org/apache/ahc/ResponseHeaderParsingTest.java   (with props)
Modified:
    geronimo/sandbox/AsyncHttpClient/src/main/java/org/apache/ahc/codec/HttpDecoder.java
    geronimo/sandbox/AsyncHttpClient/src/main/java/org/apache/ahc/codec/HttpResponseDecoder.java

Modified: geronimo/sandbox/AsyncHttpClient/src/main/java/org/apache/ahc/codec/HttpDecoder.java
URL: http://svn.apache.org/viewvc/geronimo/sandbox/AsyncHttpClient/src/main/java/org/apache/ahc/codec/HttpDecoder.java?rev=629740&r1=629739&r2=629740&view=diff
==============================================================================
--- geronimo/sandbox/AsyncHttpClient/src/main/java/org/apache/ahc/codec/HttpDecoder.java (original)
+++ geronimo/sandbox/AsyncHttpClient/src/main/java/org/apache/ahc/codec/HttpDecoder.java Thu Feb 21 02:46:41 2008
@@ -137,6 +137,75 @@
     }
 
     /**
+     * Finds a header line from a ByteBuffer that ends with a CR/LF and returns 
+     * the line as a String.  Header lines are different from other lines in one
+     * important aspect; a single header can span multiple lines.  Such folding
+     * occurs if a CR/LF is followed by a LWSP characters (a space or a
+     * horizontal tab).  This method returns complete lines that correspond to
+     * a single header if the header is folded.
+     * 
+     * @param in ByteBuffer containing data
+     * 
+     * @return a <code>String</code> representing the decoded line
+     * 
+     * @throws Exception for any Exception that is encountered
+     */
+    public String decodeHeaderLine(ByteBuffer in) throws Exception {
+        int beginPos = in.position();
+        int limit = in.limit();
+        boolean lastIsCR = false;
+        boolean lastIsCRLF = false;
+        int terminatorPos = -1;
+
+        for (int i = beginPos; i < limit; i++) {
+            byte b = in.get(i);
+            if (!lastIsCR && b == CR) {
+                lastIsCR = true;
+            } else if (lastIsCR && !lastIsCRLF && b == LF) {
+                // still need to peek one more byte... unless we begin with
+                // CRLF in which case we have encountered the end of the header
+                // section
+                if (i != beginPos + 1) {
+                    lastIsCRLF = true;
+                } else {
+                    // it is an empty line
+                    terminatorPos = i;
+                    break;
+                }
+            } else if (lastIsCRLF) {
+                // we have read until CRLF: see if there is continuation
+                if (isLWSPChar((char)b)) {
+                    // folded header; reset the flags and continue
+                    lastIsCR = false;
+                    lastIsCRLF = false;
+                } else {
+                    // we have reached the end of the line; move back by one and 
+                    // break
+                    terminatorPos = i - 1;
+                    break;
+                }
+            }
+        }
+
+        // Check if we don't have enough data to process or found a full 
+        // readable line
+        if (terminatorPos == -1) {
+            return null;
+        }
+
+        String result = null;
+        if (terminatorPos > 1) {
+            ByteBuffer line = in.slice();
+            line.limit(terminatorPos - beginPos - 1);
+            result = line.getString(decoder);
+        }
+
+        in.position(terminatorPos + 1);
+
+        return result;
+    }
+
+    /**
      * Decodes the status code and message from a HTTP response and places the values in a
      * {@link HttpResponseMessage} object.
      * 
@@ -167,6 +236,8 @@
      * @throws Exception if any exception occurs
      */
     public void decodeHeader(String line, HttpResponseMessage msg) throws Exception {
+        // first, get rid of the CRLF from linear whitespace
+        line = line.replaceAll("\\r\\n([ \\t])", "$1");
         int pos = line.indexOf(":");
         String name = line.substring(0, pos);
         String value = trimHeaderValue(line.substring(pos + 1));

Modified: geronimo/sandbox/AsyncHttpClient/src/main/java/org/apache/ahc/codec/HttpResponseDecoder.java
URL: http://svn.apache.org/viewvc/geronimo/sandbox/AsyncHttpClient/src/main/java/org/apache/ahc/codec/HttpResponseDecoder.java?rev=629740&r1=629739&r2=629740&view=diff
==============================================================================
--- geronimo/sandbox/AsyncHttpClient/src/main/java/org/apache/ahc/codec/HttpResponseDecoder.java (original)
+++ geronimo/sandbox/AsyncHttpClient/src/main/java/org/apache/ahc/codec/HttpResponseDecoder.java Thu Feb 21 02:46:41 2008
@@ -197,7 +197,7 @@
     private boolean findHeaders(HttpResponseMessage response, ByteBuffer in) throws Exception {
         //Read the headers and process them
         while (true) {
-            String line = httpDecoder.decodeLine(in);
+            String line = httpDecoder.decodeHeaderLine(in);
 
             //Check if the entire response has been read
             if (line == null) {

Added: geronimo/sandbox/AsyncHttpClient/src/test/java/org/apache/ahc/ResponseHeaderParsingTest.java
URL: http://svn.apache.org/viewvc/geronimo/sandbox/AsyncHttpClient/src/test/java/org/apache/ahc/ResponseHeaderParsingTest.java?rev=629740&view=auto
==============================================================================
--- geronimo/sandbox/AsyncHttpClient/src/test/java/org/apache/ahc/ResponseHeaderParsingTest.java (added)
+++ geronimo/sandbox/AsyncHttpClient/src/test/java/org/apache/ahc/ResponseHeaderParsingTest.java Thu Feb 21 02:46:41 2008
@@ -0,0 +1,47 @@
+package org.apache.ahc;
+
+import java.util.Arrays;
+
+import junit.framework.TestCase;
+
+import org.apache.ahc.codec.HttpResponseDecoder;
+import org.apache.ahc.codec.HttpResponseMessage;
+import org.apache.mina.common.ByteBuffer;
+import org.apache.mina.common.IoSession;
+
+public class ResponseHeaderParsingTest extends TestCase {
+    private static final String TEST_RESPONSE =
+        "HTTP/1.1 200 OK\r\n" 
+            + "Date: Fri, 15 Feb 2008 20:00:00 GMT\r\n"
+            + "Server:foo-bar\r\n"
+            + "Content-Type: text/html\r\n"
+            + "Content-Length: 13\r\n"
+            + "Connection:             close       \r\n"
+            + "Private-Header: test-continue\r\n"
+            + "\tit-keeps-going\r\n"
+            + " and-going-and-going\r\n"
+            + "\r\n"
+            + "<html></html>";
+
+    public void testParsing() throws Exception {
+        ByteBuffer buffer = ByteBuffer.allocate(TEST_RESPONSE.length());
+        buffer.put(TEST_RESPONSE.getBytes());
+        buffer.flip();
+
+        IoSession session = new FakeIoSession();
+//        session.setAttribute(HttpIoHandler.CURRENT_REQUEST, new HttpRequestMessage(null, null));
+        HttpResponseDecoder decoder = new HttpResponseDecoder();
+        FakeProtocolDecoderOutput out = new FakeProtocolDecoderOutput();
+        decoder.decode(session, buffer, out);
+
+        HttpResponseMessage response = (HttpResponseMessage)out.getObject();
+        assertEquals("Fri, 15 Feb 2008 20:00:00 GMT", response.getHeader("Date"));
+        assertEquals("foo-bar", response.getHeader("Server"));
+        assertEquals("text/html", response.getHeader("Content-Type"));
+        assertEquals("13", response.getHeader("Content-Length"));
+        assertEquals("close", response.getHeader("Connection"));
+        assertEquals("test-continue\tit-keeps-going and-going-and-going", response.getHeader("Private-Header"));
+        
+        assertTrue(Arrays.equals(response.getContent(), "<html></html>".getBytes()));
+    }
+}

Propchange: geronimo/sandbox/AsyncHttpClient/src/test/java/org/apache/ahc/ResponseHeaderParsingTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: geronimo/sandbox/AsyncHttpClient/src/test/java/org/apache/ahc/ResponseHeaderParsingTest.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: geronimo/sandbox/AsyncHttpClient/src/test/java/org/apache/ahc/ResponseHeaderParsingTest.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain