Bug fixes http://nagoya.apache.org/bugzilla/show_bug.cgi?id=16892 http://nagoya.apache.org/bugzilla/show_bug.cgi?id=14036
- Chunk-encoding problem related to non-compliant empty content streams generated by IIS has been fixed - MultipartPost method now supports "expect: 100-continue" handshake All changes are incremental. I'll commit the patch before midnight (+1 GMT) if nobody loudly objects Cheers Oleg
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.13 diff -u -r1.13 ChunkedInputStream.java --- java/org/apache/commons/httpclient/ChunkedInputStream.java 11 Feb 2003 03:23:05 -0000 1.13 +++ java/org/apache/commons/httpclient/ChunkedInputStream.java 13 Feb 2003 20:08:35 -0000 @@ -87,6 +87,7 @@ * @author Eric Johnson * @author <a href="mailto:[EMAIL PROTECTED]">Mike Bowler</a> * @author Michael Becke + * @author <a href="mailto:[EMAIL PROTECTED]">Oleg Kalnichevski</a> * * @since 2.0 * @@ -130,7 +131,7 @@ } this.in = in; this.method = method; - this.chunkSize = getChunkSizeFromInputStream(in); + this.chunkSize = getChunkSizeFromInputStream(in, false); if (chunkSize == 0) { eof = true; parseTrailerHeaders(); @@ -239,11 +240,16 @@ * comment\r\n" Positions the stream at the start of the next line. * * @param in The new input stream. + * @param required <tt>true<tt/> if a valid chunk must be present, + * <tt>false<tt/> otherwise. + * * @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 { + final InputStream in, boolean required) + throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // States: 0=normal, 1=\r was scanned, 2=inside quoted string, -1=end @@ -251,7 +257,11 @@ while (state != -1) { int b = in.read(); if (b == -1) { - throw new IOException("chunked stream ended unexpectedly"); + if (required) { + throw new IOException("chunked stream ended unexpectedly"); + } else { + return 0; + } } switch (state) { case 0: @@ -310,6 +320,21 @@ 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.43 diff -u -r1.43 HttpConnection.java --- java/org/apache/commons/httpclient/HttpConnection.java 11 Feb 2003 03:23:05 -0000 1.43 +++ java/org/apache/commons/httpclient/HttpConnection.java 13 Feb 2003 20:08:33 -0000 @@ -692,11 +692,29 @@ } /** + * Tests if input data avaialble. + * + * @return boolean <tt>true</tt> if input data is availble, + * <tt>false</tt> otherwise. + * + * @throws IOException If an IO problem occurs + * @throws IllegalStateException If the connection isn't open. + */ + public boolean responseAvaliable() + throws IOException { + LOG.trace("enter HttpConnection.responseAvaliable()"); + assertOpen(); + return this.inputStream.available() > 0; + } + + + /** * Waits for the specified number of milliseconds for input data to become available * * @param timeout_ms Number of milliseconds to wait for input data. * - * @return boolean <tt>true</tt> if input data is availble, <tt>false</tt> otherwise. + * @return boolean <tt>true</tt> if input data is availble, + * <tt>false</tt> otherwise. * * @throws IOException If an IO problem occurs * @throws IllegalStateException If the connection isn't open. @@ -704,13 +722,12 @@ public boolean waitForResponse(long timeout_ms) throws IOException, IllegalStateException { LOG.trace("enter HttpConnection.waitForResponse(int)"); - assertOpen(); if (timeout_ms < 0) { throw new IllegalArgumentException("Timeout value may not be negative"); } long overtime = System.currentTimeMillis() + timeout_ms; while (System.currentTimeMillis() < overtime) { - if (inputStream.available() > 0) { + if (responseAvaliable()) { return true; } } Index: java/org/apache/commons/httpclient/methods/EntityEnclosingMethod.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/EntityEnclosingMethod.java,v retrieving revision 1.8 diff -u -r1.8 EntityEnclosingMethod.java --- java/org/apache/commons/httpclient/methods/EntityEnclosingMethod.java 8 Feb 2003 19:22:49 -0000 1.8 +++ java/org/apache/commons/httpclient/methods/EntityEnclosingMethod.java 13 Feb 2003 20:08:38 -0000 @@ -126,7 +126,8 @@ */ private int requestContentLength = CONTENT_LENGTH_AUTO; - + /** This flag specifies whether "expect: 100-continue" handshake is + * to be used prior to sending the requesst body */ private boolean useExpectHeader = true; // ----------------------------------------------------------- Constructors Index: java/org/apache/commons/httpclient/methods/MultipartPostMethod.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/MultipartPostMethod.java,v retrieving revision 1.9 diff -u -r1.9 MultipartPostMethod.java --- java/org/apache/commons/httpclient/methods/MultipartPostMethod.java 12 Feb 2003 12:30:44 -0000 1.9 +++ java/org/apache/commons/httpclient/methods/MultipartPostMethod.java 13 Feb 2003 20:08:40 -0000 @@ -73,6 +73,7 @@ import org.apache.commons.httpclient.HttpConnection; import org.apache.commons.httpclient.HttpException; import org.apache.commons.httpclient.HttpState; +import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.methods.multipart.FilePart; import org.apache.commons.httpclient.methods.multipart.Part; import org.apache.commons.httpclient.methods.multipart.StringPart; @@ -104,6 +105,10 @@ /** The parameters for this method */ private final List parameters = new ArrayList(); + /** This flag specifies whether "expect: 100-continue" handshake is + * to be used prior to sending the request body */ + private boolean useExpectHeader = true; + /** * No-arg constructor. */ @@ -141,7 +146,6 @@ super(uri, tempDir, tempFile); } - /** * Returns <tt>"POST"</tt>. * @return <tt>"POST"</tt> @@ -151,12 +155,19 @@ } /** - * Clear my request body. + * Returns the useExpectHeader. + * @return boolean */ - public void recycle() { - LOG.trace("enter recycle()"); - super.recycle(); - parameters.clear(); + public boolean getUseExpectHeader() { + return this.useExpectHeader; + } + + /** + * Sets the useExpectHeader. + * @param value The useExpectHeader to set + */ + public void setUseExpectHeader(boolean value) { + this.useExpectHeader = value; } /** @@ -216,11 +227,14 @@ return (Part[])parameters.toArray(new Part[parameters.size()]); } /** - * Add a request header. + * Add content type header and set the <tt>Expect</tt> header + * if it has not already been set, in addition to the "standard" + * set of headers * * @param state the client state * @param conn the {@link HttpConnection} the headers will eventually be * written to + * * @throws IOException when an error occurs writing the request * @throws HttpException when a HTTP protocol error occurs */ @@ -237,6 +251,16 @@ } setRequestHeader("Content-Type", buffer.toString()); } + boolean headerPresent = (getRequestHeader("Expect") != null); + if (getUseExpectHeader() && isHttp11()) { + if (!headerPresent) { + setRequestHeader("Expect", "100-continue"); + } + } else { + if (headerPresent) { + removeRequestHeader("Expect"); + } + } } /** @@ -252,6 +276,16 @@ protected boolean writeRequestBody(HttpState state, HttpConnection conn) throws IOException, HttpException { LOG.trace("enter writeRequestBody(HttpState state, HttpConnection conn)"); + if (getRequestHeader("Expect") != null) { + if (getStatusLine() == null) { + LOG.debug("Expecting response"); + return false; + } + if (getStatusLine().getStatusCode() != HttpStatus.SC_CONTINUE) { + LOG.debug("Expecting 100-continue"); + return false; + } + } OutputStream out = conn.getRequestOutputStream(); Part.sendParts(out, getParts()); return true; @@ -280,4 +314,15 @@ throw new RuntimeException(e.toString()); } } + + + /** + * Clear my request body. + */ + public void recycle() { + LOG.trace("enter recycle()"); + super.recycle(); + parameters.clear(); + } + } 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.9 diff -u -r1.9 TestStreams.java --- test/org/apache/commons/httpclient/TestStreams.java 11 Feb 2003 03:23:05 -0000 1.9 +++ test/org/apache/commons/httpclient/TestStreams.java 13 Feb 2003 20:08:27 -0000 @@ -152,6 +152,19 @@ 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);
--------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]
