You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2011/05/13 13:04:19 UTC

svn commit: r1102665 - in /httpcomponents/httpcore/branches/4.1.x: ./ httpcore-nio/src/main/java/org/apache/http/impl/nio/codecs/ httpcore-nio/src/test/java/org/apache/http/impl/nio/codecs/ httpcore/src/main/java/org/apache/http/impl/io/ httpcore/src/t...

Author: olegk
Date: Fri May 13 11:04:18 2011
New Revision: 1102665

URL: http://svn.apache.org/viewvc?rev=1102665&view=rev
Log:
In case of an unexpected end of stream condition (the peer closed connection
prematurely) truncated Content-Length delimited message bodies to cause an I/O (merged from trunk)

Modified:
    httpcomponents/httpcore/branches/4.1.x/   (props changed)
    httpcomponents/httpcore/branches/4.1.x/RELEASE_NOTES.txt
    httpcomponents/httpcore/branches/4.1.x/httpcore-nio/src/main/java/org/apache/http/impl/nio/codecs/LengthDelimitedDecoder.java
    httpcomponents/httpcore/branches/4.1.x/httpcore-nio/src/test/java/org/apache/http/impl/nio/codecs/TestLengthDelimitedDecoder.java
    httpcomponents/httpcore/branches/4.1.x/httpcore/src/main/java/org/apache/http/impl/io/ContentLengthInputStream.java
    httpcomponents/httpcore/branches/4.1.x/httpcore/src/test/java/org/apache/http/impl/io/TestContentLengthInputStream.java

Propchange: httpcomponents/httpcore/branches/4.1.x/
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Fri May 13 11:04:18 2011
@@ -1 +1,2 @@
 /httpcomponents/httpcore/branches/ibm_compat_branch:755687-758898
+/httpcomponents/httpcore/trunk:1102241-1102657

Modified: httpcomponents/httpcore/branches/4.1.x/RELEASE_NOTES.txt
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/branches/4.1.x/RELEASE_NOTES.txt?rev=1102665&r1=1102664&r2=1102665&view=diff
==============================================================================
--- httpcomponents/httpcore/branches/4.1.x/RELEASE_NOTES.txt (original)
+++ httpcomponents/httpcore/branches/4.1.x/RELEASE_NOTES.txt Fri May 13 11:04:18 2011
@@ -1,4 +1,5 @@
 Release 4.1.1
+-------------------
 
 This is a patch release that fixes a number of non-critical issues found since release 4.1.
 
@@ -11,6 +12,12 @@ Please note that several classes and met
 Users of 4.0.x versions are advised to upgrade and replace deprecated API calls following
 recommendations in javadocs.  
 
+* In case of an unexpected end of stream condition (the peer closed connection prematurely) 
+  truncated Content-Length delimited message bodies will cause an I/O exception. Application
+  can still choose to catch and ignore ConnectionClosedException in order to accept partial 
+  message content.
+  Contributed by Oleg Kalnichevski <olegk at apache.org>
+
 * [HTTPCORE-255]: Fixed resource management in InputStreamEntity#writeTo()
   Contributed by Oleg Kalnichevski <olegk at apache.org>
 

Modified: httpcomponents/httpcore/branches/4.1.x/httpcore-nio/src/main/java/org/apache/http/impl/nio/codecs/LengthDelimitedDecoder.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/branches/4.1.x/httpcore-nio/src/main/java/org/apache/http/impl/nio/codecs/LengthDelimitedDecoder.java?rev=1102665&r1=1102664&r2=1102665&view=diff
==============================================================================
--- httpcomponents/httpcore/branches/4.1.x/httpcore-nio/src/main/java/org/apache/http/impl/nio/codecs/LengthDelimitedDecoder.java (original)
+++ httpcomponents/httpcore/branches/4.1.x/httpcore-nio/src/main/java/org/apache/http/impl/nio/codecs/LengthDelimitedDecoder.java Fri May 13 11:04:18 2011
@@ -32,6 +32,7 @@ import java.nio.ByteBuffer;
 import java.nio.channels.FileChannel;
 import java.nio.channels.ReadableByteChannel;
 
+import org.apache.http.ConnectionClosedException;
 import org.apache.http.impl.io.HttpTransportMetricsImpl;
 import org.apache.http.nio.FileContentDecoder;
 import org.apache.http.nio.reactor.SessionInputBuffer;
@@ -97,7 +98,11 @@ public class LengthDelimitedDecoder exte
         }
         if (bytesRead == -1) {
             this.completed = true;
-            return -1;
+            if (this.len < this.contentLength) {
+                throw new ConnectionClosedException(
+                        "Premature end of Content-Length delimited message body (expected: "
+                        + this.contentLength + "; received: " + this.len);
+            }
         }
         this.len += bytesRead;
         if (this.len >= this.contentLength) {
@@ -149,7 +154,11 @@ public class LengthDelimitedDecoder exte
         }
         if (bytesRead == -1) {
             this.completed = true;
-            return -1;
+            if (this.len < this.contentLength) {
+                throw new ConnectionClosedException(
+                        "Premature end of Content-Length delimited message body (expected: "
+                        + this.contentLength + "; received: " + this.len);
+            }
         }
         this.len += bytesRead;
         if (this.len >= this.contentLength) {

Modified: httpcomponents/httpcore/branches/4.1.x/httpcore-nio/src/test/java/org/apache/http/impl/nio/codecs/TestLengthDelimitedDecoder.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/branches/4.1.x/httpcore-nio/src/test/java/org/apache/http/impl/nio/codecs/TestLengthDelimitedDecoder.java?rev=1102665&r1=1102664&r2=1102665&view=diff
==============================================================================
--- httpcomponents/httpcore/branches/4.1.x/httpcore-nio/src/test/java/org/apache/http/impl/nio/codecs/TestLengthDelimitedDecoder.java (original)
+++ httpcomponents/httpcore/branches/4.1.x/httpcore-nio/src/test/java/org/apache/http/impl/nio/codecs/TestLengthDelimitedDecoder.java Fri May 13 11:04:18 2011
@@ -38,6 +38,7 @@ import java.nio.channels.ReadableByteCha
 
 import junit.framework.TestCase;
 
+import org.apache.http.ConnectionClosedException;
 import org.apache.http.impl.io.HttpTransportMetricsImpl;
 import org.apache.http.impl.nio.reactor.SessionInputBufferImpl;
 import org.apache.http.mockup.ReadableByteChannelMockup;
@@ -516,4 +517,50 @@ public class TestLengthDelimitedDecoder 
         assertEquals(0, metrics.getBytesTransferred());
     }
 
+    public void testTruncatedContent() throws Exception {
+        ReadableByteChannel channel = new ReadableByteChannelMockup(
+                new String[] {"1234567890"}, "US-ASCII");
+        HttpParams params = new BasicHttpParams();
+
+        SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, params);
+        HttpTransportMetricsImpl metrics = new HttpTransportMetricsImpl();
+        LengthDelimitedDecoder decoder = new LengthDelimitedDecoder(
+                channel, inbuf, metrics, 20);
+
+        ByteBuffer dst = ByteBuffer.allocate(1024);
+
+        int bytesRead = decoder.read(dst);
+        assertEquals(10, bytesRead);
+        try {
+            decoder.read(dst);
+            fail("ClosedChannelException should have been thrown");
+        } catch (ConnectionClosedException ex) {
+        }
+    }
+
+    public void testTruncatedContentWithFile() throws Exception {
+        ReadableByteChannel channel = new ReadableByteChannelMockup(
+                new String[] {"1234567890"}, "US-ASCII");
+        HttpParams params = new BasicHttpParams();
+
+        SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, params);
+        HttpTransportMetricsImpl metrics = new HttpTransportMetricsImpl();
+        LengthDelimitedDecoder decoder = new LengthDelimitedDecoder(
+                channel, inbuf, metrics, 20);
+
+        File fileHandle = File.createTempFile("testFile", ".txt");
+        RandomAccessFile testfile  = new RandomAccessFile(fileHandle, "rw");
+        FileChannel fchannel = testfile.getChannel();
+        long bytesRead = decoder.transfer(fchannel, 0, Integer.MAX_VALUE);
+        assertEquals(10, bytesRead);
+        try {
+            decoder.transfer(fchannel, 10, Integer.MAX_VALUE);
+            fail("ClosedChannelException should have been thrown");
+        } catch (ConnectionClosedException ex) {
+        } finally {
+            testfile.close();
+            deleteWithCheck(fileHandle);
+        }
+    }
+
 }

Modified: httpcomponents/httpcore/branches/4.1.x/httpcore/src/main/java/org/apache/http/impl/io/ContentLengthInputStream.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/branches/4.1.x/httpcore/src/main/java/org/apache/http/impl/io/ContentLengthInputStream.java?rev=1102665&r1=1102664&r2=1102665&view=diff
==============================================================================
--- httpcomponents/httpcore/branches/4.1.x/httpcore/src/main/java/org/apache/http/impl/io/ContentLengthInputStream.java (original)
+++ httpcomponents/httpcore/branches/4.1.x/httpcore/src/main/java/org/apache/http/impl/io/ContentLengthInputStream.java Fri May 13 11:04:18 2011
@@ -30,6 +30,7 @@ package org.apache.http.impl.io;
 import java.io.IOException;
 import java.io.InputStream;
 
+import org.apache.http.ConnectionClosedException;
 import org.apache.http.io.BufferInfo;
 import org.apache.http.io.SessionInputBuffer;
 
@@ -99,8 +100,10 @@ public class ContentLengthInputStream ex
     public void close() throws IOException {
         if (!closed) {
             try {
-                byte buffer[] = new byte[BUFFER_SIZE];
-                while (read(buffer) >= 0) {
+                if (pos < contentLength) {
+                    byte buffer[] = new byte[BUFFER_SIZE];
+                    while (read(buffer) >= 0) {
+                    }
                 }
             } finally {
                 // close after above so that we don't throw an exception trying
@@ -133,8 +136,17 @@ public class ContentLengthInputStream ex
         if (pos >= contentLength) {
             return -1;
         }
-        pos++;
-        return this.in.read();
+        int b = this.in.read();
+        if (b == -1) {
+            if (pos < contentLength) {
+                throw new ConnectionClosedException(
+                        "Premature end of Content-Length delimited message body (expected: "
+                        + contentLength + "; received: " + pos);
+            }
+        } else {
+            pos++;
+        }
+        return b;
     }
 
     /**
@@ -162,7 +174,14 @@ public class ContentLengthInputStream ex
             len = (int) (contentLength - pos);
         }
         int count = this.in.read(b, off, len);
-        pos += count;
+        if (count == -1 && pos < contentLength) {
+            throw new ConnectionClosedException(
+                    "Premature end of Content-Length delimited message body (expected: "
+                    + contentLength + "; received: " + pos);
+        }
+        if (count > 0) {
+            pos += count;
+        }
         return count;
     }
 

Modified: httpcomponents/httpcore/branches/4.1.x/httpcore/src/test/java/org/apache/http/impl/io/TestContentLengthInputStream.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/branches/4.1.x/httpcore/src/test/java/org/apache/http/impl/io/TestContentLengthInputStream.java?rev=1102665&r1=1102664&r2=1102665&view=diff
==============================================================================
--- httpcomponents/httpcore/branches/4.1.x/httpcore/src/test/java/org/apache/http/impl/io/TestContentLengthInputStream.java (original)
+++ httpcomponents/httpcore/branches/4.1.x/httpcore/src/test/java/org/apache/http/impl/io/TestContentLengthInputStream.java Fri May 13 11:04:18 2011
@@ -33,7 +33,9 @@ import java.io.InputStream;
 
 import junit.framework.TestCase;
 
+import org.apache.http.ConnectionClosedException;
 import org.apache.http.mockup.SessionInputBufferMockup;
+import org.apache.http.io.SessionInputBuffer;
 import org.apache.http.util.EncodingUtils;
 
 public class TestContentLengthInputStream extends TestCase {
@@ -93,10 +95,6 @@ public class TestContentLengthInputStrea
         assertTrue(in.skip(-1) == 0);
         assertTrue(in.read() == -1);
 
-        in = new ContentLengthInputStream(new SessionInputBufferMockup(new byte[2]), 4L);
-        in.read();
-        assertTrue(in.skip(2) == 1);
-
         in = new ContentLengthInputStream(new SessionInputBufferMockup(new byte[20]), 10L);
         assertEquals(5,in.skip(5));
         assertEquals(5, in.read(new byte[20]));
@@ -111,9 +109,10 @@ public class TestContentLengthInputStrea
     }
 
     public void testClose() throws IOException {
-        String correct = "1234567890123456";
-        InputStream in = new ContentLengthInputStream(new SessionInputBufferMockup(
-            EncodingUtils.getBytes(correct, CONTENT_CHARSET)), 10L);
+        String correct = "1234567890123456-";
+        SessionInputBuffer inbuffer = new SessionInputBufferMockup(
+                EncodingUtils.getBytes(correct, CONTENT_CHARSET));
+        InputStream in = new ContentLengthInputStream(inbuffer, 16L);
         in.close();
         in.close();
         try {
@@ -135,6 +134,27 @@ public class TestContentLengthInputStrea
         } catch (IOException ex) {
             // expected
         }
+        assertEquals('-', inbuffer.read());
+    }
+
+    public void testTruncatedContent() throws IOException {
+        String correct = "1234567890123456";
+        SessionInputBuffer inbuffer = new SessionInputBufferMockup(EncodingUtils.getBytes(
+                correct, CONTENT_CHARSET));
+        InputStream in = new ContentLengthInputStream(inbuffer, 32L);
+        byte[] tmp = new byte[32];
+        int byteRead = in.read(tmp);
+        assertEquals(16, byteRead);
+        try {
+            in.read(tmp);
+            fail("ConnectionClosedException should have been closed");
+        } catch (ConnectionClosedException ex) {
+        }
+        try {
+            in.read();
+            fail("ConnectionClosedException should have been closed");
+        } catch (ConnectionClosedException ex) {
+        }
     }
 
 }