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]

Reply via email to