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 2009/05/18 20:28:03 UTC

svn commit: r776038 - in /httpcomponents/httpcore/trunk: ./ httpcore-nio/src/main/java/org/apache/http/impl/nio/ httpcore-nio/src/main/java/org/apache/http/impl/nio/codecs/ httpcore-nio/src/main/java/org/apache/http/nio/util/ httpcore-nio/src/test/java...

Author: olegk
Date: Mon May 18 18:28:02 2009
New Revision: 776038

URL: http://svn.apache.org/viewvc?rev=776038&view=rev
Log:
HTTPCORE-195: Make it possible to tolerate truncated chunked-coded streams

Added:
    httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/nio/protocol/TestTruncatedChunks.java   (with props)
    httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/TruncatedChunkException.java   (with props)
Modified:
    httpcomponents/httpcore/trunk/RELEASE_NOTES.txt
    httpcomponents/httpcore/trunk/httpcore-nio/src/main/java/org/apache/http/impl/nio/NHttpConnectionBase.java
    httpcomponents/httpcore/trunk/httpcore-nio/src/main/java/org/apache/http/impl/nio/codecs/ChunkDecoder.java
    httpcomponents/httpcore/trunk/httpcore-nio/src/main/java/org/apache/http/nio/util/SimpleInputBuffer.java
    httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/mockup/TestHttpClient.java
    httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/mockup/TestHttpSSLClient.java
    httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/mockup/TestHttpSSLServer.java
    httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/mockup/TestHttpServer.java
    httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/nio/protocol/TestJob.java
    httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/nio/protocol/TestRequestHandler.java
    httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/io/ChunkedInputStream.java

Modified: httpcomponents/httpcore/trunk/RELEASE_NOTES.txt
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/RELEASE_NOTES.txt?rev=776038&r1=776037&r2=776038&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/RELEASE_NOTES.txt (original)
+++ httpcomponents/httpcore/trunk/RELEASE_NOTES.txt Mon May 18 18:28:02 2009
@@ -1,6 +1,10 @@
 Changes since 4.0
 -------------------
 
+* [HTTPCORE-195] Truncated chunk-coded streams can now be tolerated by catching and discarding 
+  TruncatedChunkException.
+  Contributed by Oleg Kalnichevski <olegk at apache.org> 
+
 * SSLIOSession#isAppOutputReady and SSLIOSession#isAppInputReady no longer ignore the application 
   event mask causing I/O event notifications for unrequested type of events.  
   Contributed by Oleg Kalnichevski <olegk at apache.org> 

Modified: httpcomponents/httpcore/trunk/httpcore-nio/src/main/java/org/apache/http/impl/nio/NHttpConnectionBase.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore-nio/src/main/java/org/apache/http/impl/nio/NHttpConnectionBase.java?rev=776038&r1=776037&r2=776038&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore-nio/src/main/java/org/apache/http/impl/nio/NHttpConnectionBase.java (original)
+++ httpcomponents/httpcore/trunk/httpcore-nio/src/main/java/org/apache/http/impl/nio/NHttpConnectionBase.java Mon May 18 18:28:02 2009
@@ -35,6 +35,8 @@
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.SocketAddress;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
 
 import org.apache.http.ConnectionClosedException;
 import org.apache.http.Header;
@@ -65,6 +67,8 @@
 import org.apache.http.nio.reactor.EventMask;
 import org.apache.http.nio.reactor.IOSession;
 import org.apache.http.nio.reactor.SessionBufferStatus;
+import org.apache.http.nio.reactor.SessionInputBuffer;
+import org.apache.http.nio.reactor.SessionOutputBuffer;
 import org.apache.http.nio.util.ByteBufferAllocator;
 import org.apache.http.params.CoreConnectionPNames;
 import org.apache.http.params.HttpConnectionParams;
@@ -203,26 +207,18 @@
     protected HttpEntity prepareDecoder(final HttpMessage message) throws HttpException {
         BasicHttpEntity entity = new BasicHttpEntity();
         long len = this.incomingContentStrategy.determineLength(message);
+        this.contentDecoder = createChunkDecoder(
+        		len,
+                this.session.channel(), 
+                this.inbuf, 
+                this.inTransportMetrics);
         if (len == ContentLengthStrategy.CHUNKED) {
-            this.contentDecoder = new ChunkDecoder(
-                    this.session.channel(), 
-                    this.inbuf, 
-                    this.inTransportMetrics);
             entity.setChunked(true);
             entity.setContentLength(-1);
         } else if (len == ContentLengthStrategy.IDENTITY) {
-            this.contentDecoder = new IdentityDecoder(
-                    this.session.channel(), 
-                    this.inbuf, 
-                    this.inTransportMetrics);
             entity.setChunked(false);
             entity.setContentLength(-1);
         } else {
-            this.contentDecoder = new LengthDelimitedDecoder(
-                    this.session.channel(), 
-                    this.inbuf, 
-                    this.inTransportMetrics,
-                    len);
             entity.setChunked(false);
             entity.setContentLength(len);
         }
@@ -237,6 +233,33 @@
         }
         return entity;
     }
+    
+    /**
+     * Factory method for {@link ContentDecoder} instances.
+     * 
+     * @since 4.1
+     * 
+     * @param len content length, if known, {@link ContentLengthStrategy#CHUNKED} or
+     *   {@link ContentLengthStrategy#IDENTITY}, if unknown. 
+     * @param channel the session channel.
+     * @param buffer the session buffer.
+     * @param metrics transport metrics.
+     * 
+     * @return content decoder.
+     */
+    protected ContentDecoder createChunkDecoder(
+    		final long len,    		
+    		final ReadableByteChannel channel, 
+            final SessionInputBuffer buffer,
+            final HttpTransportMetricsImpl metrics) {
+        if (len == ContentLengthStrategy.CHUNKED) {
+            return new ChunkDecoder(channel, buffer, metrics);
+        } else if (len == ContentLengthStrategy.IDENTITY) {
+            return new IdentityDecoder(channel, buffer, metrics);
+        } else {
+            return new LengthDelimitedDecoder(channel, buffer, metrics, len);
+        }
+    }
 
     /**
      * Initializes a specific {@link ContentEncoder} implementation based on the
@@ -247,22 +270,37 @@
      */
     protected void prepareEncoder(final HttpMessage message) throws HttpException {
         long len = this.outgoingContentStrategy.determineLength(message);
+        this.contentEncoder = createContentEncoder(
+        		len,
+                this.session.channel(),
+                this.outbuf,
+                this.outTransportMetrics);
+    }
+    
+    /**
+     * Factory method for {@link ContentEncoder} instances.
+     * 
+     * @since 4.1
+     * 
+     * @param len content length, if known, {@link ContentLengthStrategy#CHUNKED} or
+     *   {@link ContentLengthStrategy#IDENTITY}, if unknown. 
+     * @param channel the session channel.
+     * @param buffer the session buffer.
+     * @param metrics transport metrics.
+     * 
+     * @return content encoder.
+     */
+    protected ContentEncoder createContentEncoder(
+    		final long len,
+    		final WritableByteChannel channel, 
+            final SessionOutputBuffer buffer,
+            final HttpTransportMetricsImpl metrics) {
         if (len == ContentLengthStrategy.CHUNKED) {
-            this.contentEncoder = new ChunkEncoder(
-                    this.session.channel(),
-                    this.outbuf,
-                    this.outTransportMetrics);
+            return new ChunkEncoder(channel, buffer, metrics);
         } else if (len == ContentLengthStrategy.IDENTITY) {
-            this.contentEncoder = new IdentityEncoder(
-                    this.session.channel(),
-                    this.outbuf,
-                    this.outTransportMetrics);
+            return new IdentityEncoder(channel, buffer, metrics);
         } else {
-            this.contentEncoder = new LengthDelimitedEncoder(
-                    this.session.channel(),
-                    this.outbuf,
-                    this.outTransportMetrics,
-                    len);
+            return new LengthDelimitedEncoder(channel, buffer, metrics, len);
         }
     }
 

Modified: httpcomponents/httpcore/trunk/httpcore-nio/src/main/java/org/apache/http/impl/nio/codecs/ChunkDecoder.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore-nio/src/main/java/org/apache/http/impl/nio/codecs/ChunkDecoder.java?rev=776038&r1=776037&r2=776038&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore-nio/src/main/java/org/apache/http/impl/nio/codecs/ChunkDecoder.java (original)
+++ httpcomponents/httpcore/trunk/httpcore-nio/src/main/java/org/apache/http/impl/nio/codecs/ChunkDecoder.java Mon May 18 18:28:02 2009
@@ -39,6 +39,7 @@
 
 import org.apache.http.Header;
 import org.apache.http.MalformedChunkCodingException;
+import org.apache.http.TruncatedChunkException;
 import org.apache.http.message.BufferedHeader;
 import org.apache.http.nio.reactor.SessionInputBuffer;
 import org.apache.http.impl.io.HttpTransportMetricsImpl;
@@ -200,7 +201,11 @@
                 int maxLen = this.chunkSize - this.pos;
                 int len = this.buffer.read(dst, maxLen);
                 if (maxLen > 0 && len == 0 && this.endOfStream) {
-                    throw new MalformedChunkCodingException("Truncated chunk");
+                    this.state = COMPLETED;
+                    this.completed = true;
+                	throw new TruncatedChunkException("Truncated chunk "
+                    		+ "( expected size: " + this.chunkSize 
+                    		+ "; actual size: " + this.pos + ")");
                 }
                 this.pos += len;
                 totalRead += len;

Modified: httpcomponents/httpcore/trunk/httpcore-nio/src/main/java/org/apache/http/nio/util/SimpleInputBuffer.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore-nio/src/main/java/org/apache/http/nio/util/SimpleInputBuffer.java?rev=776038&r1=776037&r2=776038&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore-nio/src/main/java/org/apache/http/nio/util/SimpleInputBuffer.java (original)
+++ httpcomponents/httpcore/trunk/httpcore-nio/src/main/java/org/apache/http/nio/util/SimpleInputBuffer.java Mon May 18 18:28:02 2009
@@ -112,6 +112,7 @@
     }
 
     public void shutdown() {
+        this.endOfStream = true;
     }
     
 }

Modified: httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/mockup/TestHttpClient.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/mockup/TestHttpClient.java?rev=776038&r1=776037&r2=776038&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/mockup/TestHttpClient.java (original)
+++ httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/mockup/TestHttpClient.java Mon May 18 18:28:02 2009
@@ -65,8 +65,13 @@
         this.ioReactor.setExceptionHandler(exceptionHandler);
     }
 
+    protected IOEventDispatch createIOEventDispatch(
+    		final NHttpClientHandler clientHandler, final HttpParams params) {
+        return new DefaultClientIOEventDispatch(clientHandler, params);
+    }
+    
     private void execute(final NHttpClientHandler clientHandler) throws IOException {
-        IOEventDispatch ioEventDispatch = new DefaultClientIOEventDispatch(
+        IOEventDispatch ioEventDispatch = createIOEventDispatch(
                 clientHandler, 
                 this.params);        
         

Modified: httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/mockup/TestHttpSSLClient.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/mockup/TestHttpSSLClient.java?rev=776038&r1=776037&r2=776038&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/mockup/TestHttpSSLClient.java (original)
+++ httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/mockup/TestHttpSSLClient.java Mon May 18 18:28:02 2009
@@ -90,9 +90,16 @@
     public void setExceptionHandler(final IOReactorExceptionHandler exceptionHandler) {
         this.ioReactor.setExceptionHandler(exceptionHandler);
     }
-
+    
+    protected IOEventDispatch createIOEventDispatch(
+    		final NHttpClientHandler clientHandler, 
+            final SSLContext sslcontext,
+    		final HttpParams params) {
+        return new SSLClientIOEventDispatch(clientHandler, sslcontext, params);
+    }
+    
     private void execute(final NHttpClientHandler clientHandler) throws IOException {
-        IOEventDispatch ioEventDispatch = new SSLClientIOEventDispatch(
+        IOEventDispatch ioEventDispatch = createIOEventDispatch(
                 clientHandler, 
                 this.sslcontext,
                 this.params);

Modified: httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/mockup/TestHttpSSLServer.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/mockup/TestHttpSSLServer.java?rev=776038&r1=776037&r2=776038&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/mockup/TestHttpSSLServer.java (original)
+++ httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/mockup/TestHttpSSLServer.java Mon May 18 18:28:02 2009
@@ -97,8 +97,15 @@
         this.ioReactor.setExceptionHandler(exceptionHandler);
     }
 
+    protected IOEventDispatch createIOEventDispatch(
+    		final NHttpServiceHandler serviceHandler, 
+            final SSLContext sslcontext,
+    		final HttpParams params) {
+        return new SSLServerIOEventDispatch(serviceHandler, sslcontext, params);
+    }
+    
     private void execute(final NHttpServiceHandler serviceHandler) throws IOException {
-        IOEventDispatch ioEventDispatch = new SSLServerIOEventDispatch(
+        IOEventDispatch ioEventDispatch = createIOEventDispatch(
                 serviceHandler, 
                 this.sslcontext,
                 this.params);

Modified: httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/mockup/TestHttpServer.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/mockup/TestHttpServer.java?rev=776038&r1=776037&r2=776038&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/mockup/TestHttpServer.java (original)
+++ httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/mockup/TestHttpServer.java Mon May 18 18:28:02 2009
@@ -71,8 +71,13 @@
         this.ioReactor.setExceptionHandler(exceptionHandler);
     }
 
+    protected IOEventDispatch createIOEventDispatch(
+    		final NHttpServiceHandler serviceHandler, final HttpParams params) {
+        return new DefaultServerIOEventDispatch(serviceHandler, params);
+    }
+    
     private void execute(final NHttpServiceHandler serviceHandler) throws IOException {
-        IOEventDispatch ioEventDispatch = new DefaultServerIOEventDispatch(
+        IOEventDispatch ioEventDispatch = createIOEventDispatch(
                 serviceHandler, 
                 this.params);
         

Modified: httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/nio/protocol/TestJob.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/nio/protocol/TestJob.java?rev=776038&r1=776037&r2=776038&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/nio/protocol/TestJob.java (original)
+++ httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/nio/protocol/TestJob.java Mon May 18 18:28:02 2009
@@ -45,6 +45,7 @@
     private volatile int statusCode;
     private volatile String result;
     private volatile String failureMessage;
+    private volatile Exception ex;
     
     public TestJob(int maxCount) {
         super();
@@ -99,6 +100,10 @@
         return this.failureMessage;
     }
     
+    public Exception getException() {
+    	return this.ex;
+    }
+    
     public boolean isCompleted() {
         return this.completed;
     }
@@ -113,16 +118,21 @@
         notifyAll();
     }
     
-    public synchronized void fail(final String message) {
+    public synchronized void fail(final String message, final Exception ex) {
         if (this.completed) {
             return;
         }
         this.completed = true;
         this.result = null;
         this.failureMessage = message;
+        this.ex = ex;
         notifyAll();
     }
     
+    public void fail(final String message) {
+    	fail(message, null);
+    }
+    
     public synchronized void waitFor() throws InterruptedException {
         while (!this.completed) {
             wait();

Modified: httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/nio/protocol/TestRequestHandler.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/nio/protocol/TestRequestHandler.java?rev=776038&r1=776037&r2=776038&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/nio/protocol/TestRequestHandler.java (original)
+++ httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/nio/protocol/TestRequestHandler.java Mon May 18 18:28:02 2009
@@ -49,6 +49,17 @@
 
 final class TestRequestHandler extends SimpleNHttpRequestHandler implements HttpRequestHandler {
     
+	private final boolean chunking;
+	
+	TestRequestHandler() {
+		this(false);
+	}
+	
+	TestRequestHandler(boolean chunking) {
+		super();
+		this.chunking = chunking;
+	}
+	
     public ConsumingNHttpEntity entityRequest(
             final HttpEntityEnclosingRequest request,
             final HttpContext context) {
@@ -88,6 +99,7 @@
             content = buffer.toString();
         }
         NStringEntity entity = new NStringEntity(content, "US-ASCII");
+        entity.setChunked(this.chunking);
         response.setEntity(entity);
     }
     

Added: httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/nio/protocol/TestTruncatedChunks.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/nio/protocol/TestTruncatedChunks.java?rev=776038&view=auto
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/nio/protocol/TestTruncatedChunks.java (added)
+++ httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/nio/protocol/TestTruncatedChunks.java Mon May 18 18:28:02 2009
@@ -0,0 +1,501 @@
+/*
+ * $HeadURL$
+ * $Revision$
+ * $Date$
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.nio.protocol;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.WritableByteChannel;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.MalformedChunkCodingException;
+import org.apache.http.TruncatedChunkException;
+import org.apache.http.entity.ContentLengthStrategy;
+import org.apache.http.entity.HttpEntityWrapper;
+import org.apache.http.impl.DefaultConnectionReuseStrategy;
+import org.apache.http.impl.DefaultHttpResponseFactory;
+import org.apache.http.impl.io.HttpTransportMetricsImpl;
+import org.apache.http.impl.nio.DefaultNHttpServerConnection;
+import org.apache.http.impl.nio.DefaultServerIOEventDispatch;
+import org.apache.http.impl.nio.codecs.AbstractContentEncoder;
+import org.apache.http.impl.nio.reactor.ExceptionEvent;
+import org.apache.http.message.BasicHttpRequest;
+import org.apache.http.mockup.SimpleEventListener;
+import org.apache.http.mockup.SimpleNHttpRequestHandlerResolver;
+import org.apache.http.mockup.TestHttpClient;
+import org.apache.http.mockup.TestHttpServer;
+import org.apache.http.nio.ContentDecoder;
+import org.apache.http.nio.ContentEncoder;
+import org.apache.http.nio.IOControl;
+import org.apache.http.nio.NHttpConnection;
+import org.apache.http.nio.NHttpServerIOTarget;
+import org.apache.http.nio.NHttpServiceHandler;
+import org.apache.http.nio.entity.ConsumingNHttpEntity;
+import org.apache.http.nio.entity.ContentInputStream;
+import org.apache.http.nio.protocol.AsyncNHttpClientHandler;
+import org.apache.http.nio.protocol.AsyncNHttpServiceHandler;
+import org.apache.http.nio.protocol.NHttpRequestExecutionHandler;
+import org.apache.http.nio.reactor.IOEventDispatch;
+import org.apache.http.nio.reactor.IOSession;
+import org.apache.http.nio.reactor.ListenerEndpoint;
+import org.apache.http.nio.reactor.SessionOutputBuffer;
+import org.apache.http.nio.util.ByteBufferAllocator;
+import org.apache.http.nio.util.HeapByteBufferAllocator;
+import org.apache.http.nio.util.SimpleInputBuffer;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.CoreConnectionPNames;
+import org.apache.http.params.CoreProtocolPNames;
+import org.apache.http.params.HttpParams;
+import org.apache.http.protocol.BasicHttpProcessor;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.protocol.RequestConnControl;
+import org.apache.http.protocol.RequestContent;
+import org.apache.http.protocol.RequestExpectContinue;
+import org.apache.http.protocol.RequestTargetHost;
+import org.apache.http.protocol.RequestUserAgent;
+import org.apache.http.protocol.ResponseConnControl;
+import org.apache.http.protocol.ResponseContent;
+import org.apache.http.protocol.ResponseDate;
+import org.apache.http.protocol.ResponseServer;
+import org.apache.http.util.CharArrayBuffer;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Tests for handling truncated chunks.
+ */
+public class TestTruncatedChunks extends TestCase {
+
+    // ------------------------------------------------------------ Constructor
+    public TestTruncatedChunks(String testName) {
+        super(testName);
+    }
+
+    // ------------------------------------------------------------------- Main
+    public static void main(String args[]) {
+        String[] testCaseName = { TestTruncatedChunks.class.getName() };
+        junit.textui.TestRunner.main(testCaseName);
+    }
+
+    // ------------------------------------------------------- TestCase Methods
+
+    public static Test suite() {
+        return new TestSuite(TestTruncatedChunks.class);
+    }
+    
+    private static final byte[] GARBAGE = new byte[] {'1', '2', '3', '4', '5' };
+    
+    static class BrokenChunkEncoder extends AbstractContentEncoder {
+
+        private final CharArrayBuffer lineBuffer;
+    	private boolean done;
+        
+        public BrokenChunkEncoder(
+                final WritableByteChannel channel, 
+                final SessionOutputBuffer buffer,
+                final HttpTransportMetricsImpl metrics) {
+            super(channel, buffer, metrics);
+            this.lineBuffer = new CharArrayBuffer(16);
+        }
+
+		public void complete() throws IOException {
+        	this.completed = true;
+		}
+
+		public int write(ByteBuffer src) throws IOException {
+			int chunk;
+			if (!this.done) {
+	            this.lineBuffer.clear();
+	            this.lineBuffer.append(Integer.toHexString(GARBAGE.length * 10));
+	            this.buffer.writeLine(this.lineBuffer);
+	            this.buffer.write(ByteBuffer.wrap(GARBAGE));
+	            this.done = true;
+	            chunk = GARBAGE.length;
+			} else {
+				chunk = 0;
+			}
+	        long bytesWritten = this.buffer.flush(this.channel);
+	        if (bytesWritten > 0) {
+	            this.metrics.incrementBytesTransferred(bytesWritten);
+	        }
+	        if (!this.buffer.hasData()) {
+	        	this.channel.close();
+	        }
+			return chunk;
+		}
+    	
+    };
+
+    static class CustomServerIOEventDispatch extends DefaultServerIOEventDispatch {
+    	
+        public CustomServerIOEventDispatch(
+                final NHttpServiceHandler handler,
+                final HttpParams params) {
+            super(handler, params);
+        }
+
+		@Override
+		protected NHttpServerIOTarget createConnection(final IOSession session) {
+			
+			return new DefaultNHttpServerConnection(
+					session, 
+					createHttpRequestFactory(), 
+					this.allocator, 
+					this.params) {
+
+						@Override
+						protected ContentEncoder createContentEncoder(
+								final long len,
+								final WritableByteChannel channel,
+								final SessionOutputBuffer buffer,
+								final HttpTransportMetricsImpl metrics) {
+							if (len == ContentLengthStrategy.CHUNKED) {
+								return new BrokenChunkEncoder(channel, buffer, metrics);
+							} else {
+								return super.createContentEncoder(len, channel, buffer, metrics);
+							}
+						}
+						
+			};
+		}
+    	
+    }
+
+    static class CustomTestHttpServer extends TestHttpServer {
+        
+    	public CustomTestHttpServer(final HttpParams params) throws IOException {
+            super(params);
+        }
+
+		@Override
+		protected IOEventDispatch createIOEventDispatch(
+				NHttpServiceHandler serviceHandler, HttpParams params) {
+			return new CustomServerIOEventDispatch(serviceHandler, params);
+		}
+    	
+    }
+    
+    protected CustomTestHttpServer server;
+    protected TestHttpClient client;
+    
+    @Override
+    protected void setUp() throws Exception {
+        HttpParams serverParams = new BasicHttpParams();
+        serverParams
+            .setIntParameter(CoreConnectionPNames.SO_TIMEOUT, 60000)
+            .setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 8 * 1024)
+            .setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK, false)
+            .setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true)
+            .setParameter(CoreProtocolPNames.ORIGIN_SERVER, "TEST-SERVER/1.1");
+
+        this.server = new CustomTestHttpServer(serverParams);
+
+        HttpParams clientParams = new BasicHttpParams();
+        clientParams
+            .setIntParameter(CoreConnectionPNames.SO_TIMEOUT, 60000)
+            .setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 30000)
+            .setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 8 * 1024)
+            .setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK, false)
+            .setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true)
+            .setParameter(CoreProtocolPNames.USER_AGENT, "TEST-CLIENT/1.1");
+
+        this.client = new TestHttpClient(clientParams);
+    }
+
+    @Override
+    protected void tearDown() {
+        try {
+            this.client.shutdown();
+        } catch (IOException ex) {
+            ex.printStackTrace();
+        }
+        List<ExceptionEvent> clogs = this.client.getAuditLog();
+        if (clogs != null) {
+            for (ExceptionEvent clog: clogs) {
+                Throwable cause = clog.getCause();
+                cause.printStackTrace();
+            }
+        }
+
+        try {
+            this.server.shutdown();
+        } catch (IOException ex) {
+            ex.printStackTrace();
+        }
+        List<ExceptionEvent> slogs = this.server.getAuditLog();
+        if (slogs != null) {
+            for (ExceptionEvent slog: slogs) {
+                Throwable cause = slog.getCause();
+                cause.printStackTrace();
+            }
+        }
+    }
+    
+    public void testTruncatedChunkException() throws Exception {
+    	
+        NHttpRequestExecutionHandler requestExecutionHandler = new TestRequestExecutionHandler() {
+
+            @Override
+            protected HttpRequest generateRequest(TestJob testjob) {
+                String s = testjob.getPattern() + "x" + testjob.getCount(); 
+                return new BasicHttpRequest("GET", s);
+            }
+            
+        };
+
+        TestJob testjob = new TestJob(2000);
+        Queue<TestJob> queue = new ConcurrentLinkedQueue<TestJob>();
+        queue.add(testjob); 
+        
+        BasicHttpProcessor serverHttpProc = new BasicHttpProcessor();
+        serverHttpProc.addInterceptor(new ResponseDate());
+        serverHttpProc.addInterceptor(new ResponseServer());
+        serverHttpProc.addInterceptor(new ResponseContent());
+        serverHttpProc.addInterceptor(new ResponseConnControl());
+
+        AsyncNHttpServiceHandler serviceHandler = new AsyncNHttpServiceHandler(
+                serverHttpProc,
+                new DefaultHttpResponseFactory(),
+                new DefaultConnectionReuseStrategy(),
+                this.server.getParams());
+
+        serviceHandler.setHandlerResolver(
+                new SimpleNHttpRequestHandlerResolver(new TestRequestHandler(true)));
+        serviceHandler.setEventListener(
+                new SimpleEventListener());
+
+        BasicHttpProcessor clientHttpProc = new BasicHttpProcessor();
+        clientHttpProc.addInterceptor(new RequestContent());
+        clientHttpProc.addInterceptor(new RequestTargetHost());
+        clientHttpProc.addInterceptor(new RequestConnControl());
+        clientHttpProc.addInterceptor(new RequestUserAgent());
+        clientHttpProc.addInterceptor(new RequestExpectContinue());
+
+        AsyncNHttpClientHandler clientHandler = new AsyncNHttpClientHandler(
+                clientHttpProc,
+                requestExecutionHandler,
+                new DefaultConnectionReuseStrategy(),
+                this.client.getParams());
+
+        clientHandler.setEventListener(
+                new SimpleEventListener() {
+
+					@Override
+					public void fatalIOException(
+							final IOException ex,
+							final NHttpConnection conn) {
+						HttpContext context = conn.getContext();
+						TestJob testjob = (TestJob) context.getAttribute("job");
+						testjob.fail(ex.getMessage(), ex);
+					}
+                	
+                });
+        
+        this.server.start(serviceHandler);
+        this.client.start(clientHandler);
+
+        ListenerEndpoint endpoint = this.server.getListenerEndpoint();
+        endpoint.waitFor();
+        InetSocketAddress serverAddress = (InetSocketAddress) endpoint.getAddress();
+
+        this.client.openConnection(
+                new InetSocketAddress("localhost", serverAddress.getPort()),
+                queue);
+
+        testjob.waitFor();
+        assertFalse(testjob.isSuccessful());
+        assertNotNull(testjob.getException());
+        assertTrue(testjob.getException() instanceof MalformedChunkCodingException);
+    }
+
+    static class LenientNHttpEntity extends HttpEntityWrapper implements ConsumingNHttpEntity {
+    	
+        private final static int BUFFER_SIZE = 2048;
+
+        private final SimpleInputBuffer buffer;
+        private boolean finished;
+        private boolean consumed;
+
+        public LenientNHttpEntity(
+                final HttpEntity httpEntity,
+                final ByteBufferAllocator allocator) {
+            super(httpEntity);
+            this.buffer = new SimpleInputBuffer(BUFFER_SIZE, allocator);
+        }
+
+        public void consumeContent(
+                final ContentDecoder decoder,
+                final IOControl ioctrl) throws IOException {
+        	try {
+                this.buffer.consumeContent(decoder);
+                if (decoder.isCompleted()) {
+                    this.finished = true;
+                }
+        	} catch (TruncatedChunkException ex) {
+        		this.buffer.shutdown();
+                this.finished = true;
+        	}
+        }
+
+        public void finish() {
+            this.finished = true;
+        }
+
+        @Override
+        public void consumeContent() throws IOException {
+        }
+
+        @Override
+        public InputStream getContent() throws IOException {
+            if (!this.finished) {
+                throw new IllegalStateException("Entity content has not been fully received");
+            }
+            if (this.consumed) {
+                throw new IllegalStateException("Entity content has been consumed");
+            }
+            this.consumed = true;
+            return new ContentInputStream(this.buffer);
+        }
+
+        @Override
+        public boolean isRepeatable() {
+            return false;
+        }
+
+        @Override
+        public boolean isStreaming() {
+            return true;
+        }
+
+        @Override
+        public void writeTo(final OutputStream outstream) throws IOException {
+            if (outstream == null) {
+                throw new IllegalArgumentException("Output stream may not be null");
+            }
+            InputStream instream = getContent();
+            byte[] buffer = new byte[BUFFER_SIZE];
+            int l;
+            // consume until EOF
+            while ((l = instream.read(buffer)) != -1) {
+                outstream.write(buffer, 0, l);
+            }
+        }
+    	
+    }
+    
+    public void testIgnoreTruncatedChunkException() throws Exception {
+    	
+        NHttpRequestExecutionHandler requestExecutionHandler = new TestRequestExecutionHandler() {
+
+            @Override
+            protected HttpRequest generateRequest(final TestJob testjob) {
+                String s = testjob.getPattern() + "x" + testjob.getCount(); 
+                return new BasicHttpRequest("GET", s);
+            }
+
+			@Override
+			public ConsumingNHttpEntity responseEntity(
+					final HttpResponse response, 
+					final HttpContext context) throws IOException {
+		        return new LenientNHttpEntity(response.getEntity(),
+		                new HeapByteBufferAllocator());
+			}
+            
+        };
+
+        TestJob testjob = new TestJob(2000);
+        Queue<TestJob> queue = new ConcurrentLinkedQueue<TestJob>();
+        queue.add(testjob); 
+        
+        BasicHttpProcessor serverHttpProc = new BasicHttpProcessor();
+        serverHttpProc.addInterceptor(new ResponseDate());
+        serverHttpProc.addInterceptor(new ResponseServer());
+        serverHttpProc.addInterceptor(new ResponseContent());
+        serverHttpProc.addInterceptor(new ResponseConnControl());
+
+        AsyncNHttpServiceHandler serviceHandler = new AsyncNHttpServiceHandler(
+                serverHttpProc,
+                new DefaultHttpResponseFactory(),
+                new DefaultConnectionReuseStrategy(),
+                this.server.getParams());
+
+        serviceHandler.setHandlerResolver(
+                new SimpleNHttpRequestHandlerResolver(new TestRequestHandler(true)));
+        serviceHandler.setEventListener(
+                new SimpleEventListener());
+
+        BasicHttpProcessor clientHttpProc = new BasicHttpProcessor();
+        clientHttpProc.addInterceptor(new RequestContent());
+        clientHttpProc.addInterceptor(new RequestTargetHost());
+        clientHttpProc.addInterceptor(new RequestConnControl());
+        clientHttpProc.addInterceptor(new RequestUserAgent());
+        clientHttpProc.addInterceptor(new RequestExpectContinue());
+
+        AsyncNHttpClientHandler clientHandler = new AsyncNHttpClientHandler(
+                clientHttpProc,
+                requestExecutionHandler,
+                new DefaultConnectionReuseStrategy(),
+                this.client.getParams());
+
+        clientHandler.setEventListener(
+                new SimpleEventListener());
+        
+        this.server.start(serviceHandler);
+        this.client.start(clientHandler);
+
+        ListenerEndpoint endpoint = this.server.getListenerEndpoint();
+        endpoint.waitFor();
+        InetSocketAddress serverAddress = (InetSocketAddress) endpoint.getAddress();
+
+        this.client.openConnection(
+                new InetSocketAddress("localhost", serverAddress.getPort()),
+                queue);
+
+        testjob.waitFor();
+        if (testjob.isSuccessful()) {
+            assertEquals(HttpStatus.SC_OK, testjob.getStatusCode());
+            assertEquals(new String(GARBAGE, "US-ASCII"), testjob.getResult());
+        } else {
+            fail(testjob.getFailureMessage());
+        }
+    }
+
+}

Propchange: httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/nio/protocol/TestTruncatedChunks.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/nio/protocol/TestTruncatedChunks.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Propchange: httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/nio/protocol/TestTruncatedChunks.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/TruncatedChunkException.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/TruncatedChunkException.java?rev=776038&view=auto
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/TruncatedChunkException.java (added)
+++ httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/TruncatedChunkException.java Mon May 18 18:28:02 2009
@@ -0,0 +1,52 @@
+/*
+ * $HeadURL$
+ * $Revision$
+ * $Date$
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http;
+
+/**
+ * Signals a truncated chunk in a chunked stream.
+ *
+ * @since 4.1
+ */
+public class TruncatedChunkException extends MalformedChunkCodingException {
+
+    private static final long serialVersionUID = -23506263930279460L;
+
+    /**
+     * Creates a TruncatedChunkException with the specified detail message.
+     * 
+     * @param message The exception detail message 
+     */
+    public TruncatedChunkException(final String message) {
+        super(message);
+    }
+
+}

Propchange: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/TruncatedChunkException.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/TruncatedChunkException.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Propchange: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/TruncatedChunkException.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/io/ChunkedInputStream.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/io/ChunkedInputStream.java?rev=776038&r1=776037&r2=776038&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/io/ChunkedInputStream.java (original)
+++ httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/io/ChunkedInputStream.java Mon May 18 18:28:02 2009
@@ -37,6 +37,7 @@
 import org.apache.http.Header;
 import org.apache.http.HttpException;
 import org.apache.http.MalformedChunkCodingException;
+import org.apache.http.TruncatedChunkException;
 import org.apache.http.io.SessionInputBuffer;
 import org.apache.http.util.CharArrayBuffer;
 import org.apache.http.util.ExceptionUtils;
@@ -171,7 +172,10 @@
             }
             return bytesRead;
         } else {
-            throw new MalformedChunkCodingException("Truncated chunk");
+        	eof = true;
+        	throw new TruncatedChunkException("Truncated chunk "
+            		+ "( expected size: " + chunkSize 
+            		+ "; actual size: " + pos + ")");
         }
     }