Index: java/org/apache/commons/httpclient/ChunkedInputStream.java
===================================================================
RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/ChunkedInputStream.java,v
retrieving revision 1.15
diff -u -r1.15 ChunkedInputStream.java
--- java/org/apache/commons/httpclient/ChunkedInputStream.java 16 Feb 2003 13:08:32 -0000 1.15
+++ java/org/apache/commons/httpclient/ChunkedInputStream.java 6 May 2003 16:52:28 -0000
@@ -102,6 +102,9 @@
/** The current position within the current chunk */
private int pos;
+ /** True if we'are at the beginning of stream */
+ private boolean bof = true;
+
/** True if we've reached the end of stream */
private boolean eof = false;
@@ -122,19 +125,14 @@
public ChunkedInputStream(
final InputStream in, final HttpMethod method) throws IOException {
- if (null == in) {
- throw new NullPointerException("InputStream parameter");
+ if (in == null) {
+ throw new IllegalArgumentException("InputStream parameter may not be null");
}
- if (null == method) {
- throw new NullPointerException("HttpMethod parameter");
+ if (method == null) {
+ throw new IllegalArgumentException("HttpMethod parameter may not be null");
}
this.in = in;
this.method = method;
- this.chunkSize = getChunkSizeFromInputStream(in, false);
- if (chunkSize == 0) {
- eof = true;
- parseTrailerHeaders();
- }
this.pos = 0;
}
@@ -215,17 +213,29 @@
}
/**
- * Read the next chunk.
+ * Read the CRLF terminator.
* @throws IOException If an IO error occurs.
*/
- private void nextChunk() throws IOException {
+ private void readCRLF() throws IOException {
int cr = in.read();
int lf = in.read();
if ((cr != '\r') || (lf != '\n')) {
throw new IOException(
"CRLF expected at end of chunk: " + cr + "/" + lf);
}
+ }
+
+
+ /**
+ * Read the next chunk.
+ * @throws IOException If an IO error occurs.
+ */
+ private void nextChunk() throws IOException {
+ if (!bof) {
+ readCRLF();
+ }
chunkSize = getChunkSizeFromInputStream(in);
+ bof = false;
pos = 0;
if (chunkSize == 0) {
eof = true;
@@ -246,8 +256,7 @@
*
* @throws IOException when the chunk size could not be parsed
*/
- private static int getChunkSizeFromInputStream(
- final InputStream in, boolean required)
+ private static int getChunkSizeFromInputStream(final InputStream in)
throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -256,11 +265,7 @@
while (state != -1) {
int b = in.read();
if (b == -1) {
- if (required) {
- throw new IOException("chunked stream ended unexpectedly");
- } else {
- return 0;
- }
+ throw new IOException("chunked stream ended unexpectedly");
}
switch (state) {
case 0:
@@ -319,21 +324,6 @@
return result;
}
- /**
- * Expects the stream to start with a chunksize in hex with optional
- * comments after a semicolon. The line must end with a CRLF: "a3; some
- * comment\r\n" Positions the stream at the start of the next line.
- *
- * @param in The new input stream.
- *
- * @return the chunk size as integer
- *
- * @throws IOException when the chunk size could not be parsed
- */
- private static int getChunkSizeFromInputStream(final InputStream in)
- throws IOException {
- return getChunkSizeFromInputStream(in, true);
- }
/**
* Reads and stores the Trailer headers.
* @throws IOException If an IO problem occurs
Index: java/org/apache/commons/httpclient/HttpConnection.java
===================================================================
RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpConnection.java,v
retrieving revision 1.63
diff -u -r1.63 HttpConnection.java
--- java/org/apache/commons/httpclient/HttpConnection.java 1 May 2003 23:21:27 -0000 1.63
+++ java/org/apache/commons/httpclient/HttpConnection.java 6 May 2003 16:52:29 -0000
@@ -826,7 +826,8 @@
}
/**
- * Tests if input data avaialble.
+ * Tests if input data avaialble. This method returns immediately
+ * and does not perform any read operations on the input socket
*
* @return boolean true if input data is availble,
* false otherwise.
@@ -841,6 +842,41 @@
}
/**
+ * Tests if input data becomes available within the given period time in milliseconds.
+ *
+ * @param timeout The number milliseconds to wait for input data to become available
+ * @return boolean true if input data is availble,
+ * false otherwise.
+ *
+ * @throws IOException If an IO problem occurs
+ * @throws IllegalStateException If the connection isn't open.
+ */
+ public boolean isResponseAvailable(int timeout)
+ throws IOException {
+ assertOpen();
+ boolean result = false;
+ if (this.inputStream.available() > 0) {
+ result = true;
+ } else {
+ try {
+ this.socket.setSoTimeout(timeout);
+ int byteRead = inputStream.read();
+ if (byteRead != -1) {
+ inputStream.unread(byteRead);
+ result = true;
+ }
+ } catch (InterruptedIOException e) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Input data not available after " + timeout + " ms");
+ }
+ } finally {
+ socket.setSoTimeout(soTimeout);
+ }
+ }
+ return result;
+ }
+
+ /**
* Write the specified bytes to my output stream.
*
* @param data the data to be written
@@ -977,10 +1013,9 @@
}
/**
- * Read up to "\r\n" from my (unchunked) input stream.
+ * Read up to "\n" from my (unchunked) input stream.
* If the stream ends before the line terminator is found,
* the last part of the string will still be returned.
- * '\r' and '\n' are allowed to appear individually in the stream.
*
* @throws IllegalStateException if I am not connected
* @throws IOException if an I/O problem occurs
@@ -1352,5 +1387,4 @@
/** the connection manager that created this connection or null */
private HttpConnectionManager httpConnectionManager;
-
}
Index: java/org/apache/commons/httpclient/HttpMethodBase.java
===================================================================
RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodBase.java,v
retrieving revision 1.144
diff -u -r1.144 HttpMethodBase.java
--- java/org/apache/commons/httpclient/HttpMethodBase.java 2 May 2003 22:06:10 -0000 1.144
+++ java/org/apache/commons/httpclient/HttpMethodBase.java 6 May 2003 16:52:30 -0000
@@ -1966,7 +1966,11 @@
// RFC2616, 4.4 item number 3
if (transferEncodingHeader != null) {
if ("chunked".equalsIgnoreCase(transferEncodingHeader.getValue())) {
- result = new ChunkedInputStream(is, this);
+ // Some HTTP servers do not bother sending a closing chunk
+ // if response body is empty
+ if (conn.isResponseAvailable(conn.getSoTimeout())) {
+ result = new ChunkedInputStream(is, this);
+ }
}
} else {
int expectedLength = getResponseContentLength();
Index: java/org/apache/commons/httpclient/MultiThreadedHttpConnectionManager.java
===================================================================
RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/MultiThreadedHttpConnectionManager.java,v
retrieving revision 1.15
diff -u -r1.15 MultiThreadedHttpConnectionManager.java
--- java/org/apache/commons/httpclient/MultiThreadedHttpConnectionManager.java 27 Apr 2003 19:43:41 -0000 1.15
+++ java/org/apache/commons/httpclient/MultiThreadedHttpConnectionManager.java 6 May 2003 16:52:30 -0000
@@ -831,6 +831,14 @@
}
}
+ public boolean isResponseAvailable(int timeout) throws IOException {
+ if (hasConnection()) {
+ return wrappedConnection.isResponseAvailable(timeout);
+ } else {
+ return false;
+ }
+ }
+
public boolean isSecure() {
if (hasConnection()) {
return wrappedConnection.isSecure();
Index: java/org/apache/commons/httpclient/methods/HeadMethod.java
===================================================================
RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/HeadMethod.java,v
retrieving revision 1.17
diff -u -r1.17 HeadMethod.java
--- java/org/apache/commons/httpclient/methods/HeadMethod.java 2 Feb 2003 04:30:13 -0000 1.17
+++ java/org/apache/commons/httpclient/methods/HeadMethod.java 6 May 2003 16:52:30 -0000
@@ -66,6 +66,7 @@
import java.io.IOException;
import org.apache.commons.httpclient.HttpConnection;
+import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethodBase;
import org.apache.commons.httpclient.HttpState;
import org.apache.commons.logging.Log;
@@ -91,6 +92,7 @@
* @author Remy Maucherat
* @author Mike Bowler
* @author Jeff Dever
+ * @author oleg Kalnichevski
*
* @version $Revision: 1.17 $
* @since 1.0
@@ -101,6 +103,8 @@
/** Log object for this class. */
private static final Log LOG = LogFactory.getLog(HeadMethod.class);
+ private int bodyCheckTimeout = -1; /* Disabled per default */
+
//~ Constructors ···························································
/**
@@ -163,10 +167,46 @@
throws IOException {
LOG.trace(
"enter HeadMethod.readResponseBody(HttpState, HttpConnection)");
+
+ if (this.bodyCheckTimeout < 0) {
+ responseBodyConsumed();
+ } else {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Check for non-compliant response body. Timeout in "
+ + this.bodyCheckTimeout + " ms");
+ }
+ if (conn.isResponseAvailable(this.bodyCheckTimeout)) {
+ if (isStrictMode()) {
+ throw new HttpException(
+ "Body content may not be sent in response to HTTP HEAD request");
+ } else {
+ LOG.warn("Body content returned in response to HTTP HEAD");
+ }
+ super.readResponseBody(state, conn);
+ }
+ }
- // despite the possible presence of a content-length header,
- // HEAD returns no response body
- responseBodyConsumed();
- return;
}
+ /**
+ * Return non-compliant response body check timeout.
+ *
+ * @return The period of time in milliseconds to wait for a response
+ * body from a non-compliant server. -1 returned when
+ * non-compliant response body check is disabled
+ */
+ public int getBodyCheckTimeout() {
+ return this.bodyCheckTimeout;
+ }
+
+ /**
+ * Set non-compliant response body check timeout.
+ *
+ * @param timeout The period of time in milliseconds to wait for a response
+ * body from a non-compliant server. -1 can be used to
+ * disable non-compliant response body check
+ */
+ public void setBodyCheckTimeout(int timeout) {
+ this.bodyCheckTimeout = timeout;
+ }
+
}
Index: test/org/apache/commons/httpclient/NoncompliantHeadMethod.java
===================================================================
RCS file: test/org/apache/commons/httpclient/NoncompliantHeadMethod.java
diff -N test/org/apache/commons/httpclient/NoncompliantHeadMethod.java
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ test/org/apache/commons/httpclient/NoncompliantHeadMethod.java 6 May 2003 16:52:31 -0000
@@ -0,0 +1,90 @@
+/*
+ * ====================================================================
+ *
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 2003 The Apache Software Foundation. All rights
+ * reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. The end-user documentation included with the redistribution, if
+ * any, must include the following acknowlegement:
+ * "This product includes software developed by the
+ * Apache Software Foundation (http://www.apache.org/)."
+ * Alternately, this acknowlegement may appear in the software itself,
+ * if and wherever such third-party acknowlegements normally appear.
+ *
+ * 4. The names "The Jakarta Project", "Commons", and "Apache Software
+ * Foundation" must not be used to endorse or promote products derived
+ * from this software without prior written permission. For written
+ * permission, please contact apache@apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache"
+ * nor may "Apache" appear in their names without prior written
+ * permission of the Apache Group.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * 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
+ * .
+ *
+ * [Additional notices, if required by prior licensing conditions]
+ *
+ */
+package org.apache.commons.httpclient;
+
+import org.apache.commons.httpclient.methods.HeadMethod;
+
+/**
+ * HTTP GET methid intended to simulate side-effects of
+ * interaction with non-compiant HTTP servers or proxies
+ *
+ * @author Oleg Kalnichevski
+ */
+
+public class NoncompliantHeadMethod extends HeadMethod {
+
+ public NoncompliantHeadMethod(){
+ super();
+ }
+
+ public NoncompliantHeadMethod(String uri) {
+ super(uri);
+ }
+
+ /**
+ * Expect HTTP HEAD but perform HTTP GET instead in order to
+ * simulate the behaviour of a non-compliant HTTP server sending
+ * body content in response to HTTP HEAD request
+ *
+ */
+ public String getName() {
+ return "GET";
+ }
+
+}
Index: test/org/apache/commons/httpclient/SimpleHttpConnection.java
===================================================================
RCS file: /home/cvspublic/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/SimpleHttpConnection.java,v
retrieving revision 1.14
diff -u -r1.14 SimpleHttpConnection.java
--- test/org/apache/commons/httpclient/SimpleHttpConnection.java 27 Apr 2003 19:43:42 -0000 1.14
+++ test/org/apache/commons/httpclient/SimpleHttpConnection.java 6 May 2003 16:52:31 -0000
@@ -91,7 +91,7 @@
Vector headers = new Vector();
Vector bodies = new Vector();
- ByteArrayInputStream inputStream;
+ InputStream inputStream;
ByteArrayOutputStream bodyOutputStream = null;
@@ -190,6 +190,15 @@
}
}
+ public boolean isResponseAvailable() throws IOException {
+ assertOpen();
+ return inputStream.available() > 0;
+ }
+
+ public boolean isResponseAvailable(int timeout) throws IOException {
+ return isResponseAvailable();
+ }
+
public void write(byte[] data)
throws IOException, IllegalStateException, HttpRecoverableException {
}
@@ -216,6 +225,5 @@
public void flushRequestOutputStream() throws IOException {
assertOpen();
}
-
}
Index: test/org/apache/commons/httpclient/TestStreams.java
===================================================================
RCS file: /home/cvspublic/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestStreams.java,v
retrieving revision 1.10
diff -u -r1.10 TestStreams.java
--- test/org/apache/commons/httpclient/TestStreams.java 13 Feb 2003 21:31:53 -0000 1.10
+++ test/org/apache/commons/httpclient/TestStreams.java 6 May 2003 16:52:32 -0000
@@ -152,19 +152,6 @@
assertEquals(0, out.size());
}
- public void testNoncompliantEmptyChunkedInputStream() throws IOException {
- HttpMethod method = new SimpleHttpMethod();
-
- InputStream in = new ChunkedInputStream(new ByteArrayInputStream(new byte[] {}), method);
- byte[] buffer = new byte[300];
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- int len;
- while ((len = in.read(buffer)) > 0) {
- out.write(buffer, 0, len);
- }
- assertEquals(0, out.size());
- }
-
public void testContentLengthInputStream() throws IOException {
String correct = "1234567890123456";
InputStream in = new ContentLengthInputStream(new ByteArrayInputStream(HttpConstants.getBytes(correct)), 10);
Index: test/org/apache/commons/httpclient/TestWebappNoncompliant.java
===================================================================
RCS file: /home/cvspublic/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestWebappNoncompliant.java,v
retrieving revision 1.4
diff -u -r1.4 TestWebappNoncompliant.java
--- test/org/apache/commons/httpclient/TestWebappNoncompliant.java 16 Mar 2003 00:09:18 -0000 1.4
+++ test/org/apache/commons/httpclient/TestWebappNoncompliant.java 6 May 2003 16:52:32 -0000
@@ -128,5 +128,39 @@
}
-
+ /**
+ * Test if a response to HEAD method from non-compliant server
+ * that contains an unexpected body content can be correctly redirected
+ */
+
+ public void testNoncompliantHeadWithResponseBody()
+ throws Exception {
+ HttpClient client = createHttpClient();
+ HeadMethod method = new NoncompliantHeadMethod("/" + getWebappContext() + "/redirect");
+ method.setBodyCheckTimeout(50);
+ client.executeMethod(method);
+ assertEquals(200,method.getStatusCode());
+ method.releaseConnection();
+ }
+
+ /**
+ * Test if a response to HEAD method from non-compliant server
+ * causes an HttpException to be thrown
+ */
+
+ public void testNoncompliantHeadStrictMode()
+ throws Exception {
+ HttpClient client = createHttpClient();
+ client.setStrictMode(true);
+ HeadMethod method = new NoncompliantHeadMethod("/" + getWebappContext() + "/body");
+ method.setBodyCheckTimeout(50);
+ try {
+ client.executeMethod(method);
+ fail("HttpException should have been thrown");
+ } catch(HttpException e) {
+ // Expected
+ }
+ method.releaseConnection();
+ }
+
}