Index: examples/MultipartFileUploadApp.java
===================================================================
RCS file: /home/cvs/jakarta-commons/httpclient/src/examples/MultipartFileUploadApp.java,v
retrieving revision 1.9
diff -u -r1.9 MultipartFileUploadApp.java
--- examples/MultipartFileUploadApp.java 22 Feb 2004 18:08:45 -0000 1.9
+++ examples/MultipartFileUploadApp.java 29 Sep 2004 02:40:27 -0000
@@ -39,8 +39,8 @@
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
-import javax.swing.JComboBox;
import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
@@ -50,7 +50,10 @@
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
-import org.apache.commons.httpclient.methods.MultipartPostMethod;
+import org.apache.commons.httpclient.methods.PostMethod;
+import org.apache.commons.httpclient.methods.multipart.FilePart;
+import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
+import org.apache.commons.httpclient.methods.multipart.Part;
import org.apache.commons.httpclient.params.HttpMethodParams;
/**
@@ -147,14 +150,18 @@
cmbURLModel.addElement(targetURL);
}
- MultipartPostMethod filePost =
- new MultipartPostMethod(targetURL);
+ PostMethod filePost = new PostMethod(targetURL);
filePost.getParams().setBooleanParameter(HttpMethodParams.USE_EXPECT_CONTINUE,
cbxExpectHeader.isSelected());
try {
appendMessage("Uploading " + targetFile.getName() + " to " + targetURL);
- filePost.addParameter(targetFile.getName(), targetFile);
+ Part[] parts = {
+ new FilePart(targetFile.getName(), targetFile)
+ };
+ filePost.setRequestEntity(
+ new MultipartRequestEntity(parts, filePost.getParams())
+ );
HttpClient client = new HttpClient();
client.getHttpConnectionManager().
getParams().setConnectionTimeout(5000);
Index: java/org/apache/commons/httpclient/methods/MultipartPostMethod.java
===================================================================
RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/MultipartPostMethod.java,v
retrieving revision 1.26
diff -u -r1.26 MultipartPostMethod.java
--- java/org/apache/commons/httpclient/methods/MultipartPostMethod.java 13 Jun 2004 20:22:19 -0000 1.26
+++ java/org/apache/commons/httpclient/methods/MultipartPostMethod.java 29 Sep 2004 02:40:29 -0000
@@ -71,6 +71,9 @@
* @author Oleg Kalnichevski
*
* @since 2.0
+ *
+ * @deprecated Use {@link org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity}
+ * in conjunction with {@link org.apache.commons.httpclient.methods.PostMethod} instead.
*/
public class MultipartPostMethod extends ExpectContinueMethod {
Index: java/org/apache/commons/httpclient/methods/multipart/Part.java
===================================================================
RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/multipart/Part.java,v
retrieving revision 1.14
diff -u -r1.14 Part.java
--- java/org/apache/commons/httpclient/methods/multipart/Part.java 18 Apr 2004 23:51:37 -0000 1.14
+++ java/org/apache/commons/httpclient/methods/multipart/Part.java 29 Sep 2004 02:40:30 -0000
@@ -53,13 +53,23 @@
/** Log object for this class. */
private static final Log LOG = LogFactory.getLog(Part.class);
- //TODO: Make this configurable
-
- /** The boundary */
+ /**
+ * The boundary
+ * @deprecated use {@link org.apache.commons.httpclient.params.HttpMethodParams#MULTIPART_BOUNDARY}
+ */
protected static final String BOUNDARY = "----------------314159265358979323846";
- /** The boundary as a byte array */
+ /**
+ * The boundary as a byte array.
+ * @deprecated
+ */
protected static final byte[] BOUNDARY_BYTES = EncodingUtil.getAsciiBytes(BOUNDARY);
+
+ /**
+ * The default boundary to be used if {@link #setBoundaryBytes(byte[])) has not
+ * been called.
+ */
+ private static final byte[] DEFAULT_BOUNDARY_BYTES = BOUNDARY_BYTES;
/** Carriage return/linefeed */
protected static final String CRLF = "\r\n";
@@ -112,10 +122,16 @@
/**
* Return the boundary string.
* @return the boundary string
+ * @deprecated
*/
public static String getBoundary() {
return BOUNDARY;
}
+
+ /**
+ * The ASCII bytes to use as the multipart boundary.
+ */
+ private byte[] boundaryBytes;
/**
* Return the name of this part.
@@ -143,6 +159,39 @@
public abstract String getTransferEncoding();
/**
+ * Gets the part boundary to be used.
+ * @return
+ */
+ protected byte[] getPartBoundary() {
+ if (boundaryBytes == null) {
+ // custom boundary bytes have not been set, use the default.
+ return DEFAULT_BOUNDARY_BYTES;
+ } else {
+ return boundaryBytes;
+ }
+ }
+
+ /**
+ * Sets the part boundary. Only meant to be used by
+ * {@link Part#sendParts(OutputStream, Part[], byte[])}
+ * and {@link Part#getLengthOfParts(Part[], byte[])}
+ * @param boundaryBytes An array of ASCII bytes.
+ */
+ void setPartBoundary(byte[] boundaryBytes) {
+ this.boundaryBytes = boundaryBytes;
+ }
+
+ /**
+ * Tests if this part can be sent more than once.
+ * @return true if {@link #sendData(OutputStream)} can be successfully called
+ * more than once.
+ * @since 3.0
+ */
+ public boolean isRepeatable() {
+ return true;
+ }
+
+ /**
* Write the start to the specified output stream
* @param out The output stream
* @throws IOException If an IO problem occurs.
@@ -150,7 +199,7 @@
protected void sendStart(OutputStream out) throws IOException {
LOG.trace("enter sendStart(OutputStream out)");
out.write(EXTRA_BYTES);
- out.write(BOUNDARY_BYTES);
+ out.write(getPartBoundary());
out.write(CRLF_BYTES);
}
@@ -291,49 +340,82 @@
}
/**
- * Write all parts and the last boundary to the specified output stream
+ * Write all parts and the last boundary to the specified output stream.
*
- * @param out The output stream
- * @param parts The array of parts to be sent
+ * @param out The stream to write to.
+ * @param parts The parts to write.
*
- * @throws IOException If an IO problem occurs.
+ * @throws IOException If an I/O error occurs while writing the parts.
*/
public static void sendParts(OutputStream out, final Part[] parts)
- throws IOException {
- LOG.trace("enter sendParts(OutputStream out, Parts[])");
+ throws IOException {
+ sendParts(out, parts, DEFAULT_BOUNDARY_BYTES);
+ }
+
+ /**
+ * Write all parts and the last boundary to the specified output stream.
+ *
+ * @param out The stream to write to.
+ * @param parts The parts to write.
+ * @param partBoundary The ASCII bytes to use as the part boundary.
+ *
+ * @throws IOException If an I/O error occurs while writing the parts.
+ */
+ public static void sendParts(OutputStream out, Part[] parts, byte[] partBoundary)
+ throws IOException {
+
if (parts == null) {
throw new IllegalArgumentException("Parts may not be null");
}
+ if (partBoundary == null || partBoundary.length == 0) {
+ throw new IllegalArgumentException("partBoundary may not be empty");
+ }
for (int i = 0; i < parts.length; i++) {
+ // set the part boundary before the part is sent
+ parts[i].setPartBoundary(partBoundary);
parts[i].send(out);
}
out.write(EXTRA_BYTES);
- out.write(BOUNDARY_BYTES);
+ out.write(partBoundary);
out.write(EXTRA_BYTES);
out.write(CRLF_BYTES);
}
-
+
/**
* Return the total sum of all parts and that of the last boundary
*
- * @param parts The array of parts
- *
- * @return the total length
+ * @param parts The parts.
+ * @return The total length
*
- * @throws IOException If an IO problem occurs.
+ * @throws IOException If an I/O error occurs while writing the parts.
*/
- public static long getLengthOfParts(final Part[] parts)
+ public static long getLengthOfParts(Part[] parts)
throws IOException {
+ return getLengthOfParts(parts, DEFAULT_BOUNDARY_BYTES);
+ }
+
+ /**
+ * Gets the length of the multipart message including the given parts.
+ *
+ * @param parts The parts.
+ * @param partBoundary The ASCII bytes to use as the part boundary.
+ * @return The total length
+ *
+ * @throws IOException If an I/O error occurs while writing the parts.
+ */
+ public static long getLengthOfParts(Part[] parts, byte[] partBoundary) throws IOException {
LOG.trace("getLengthOfParts(Parts[])");
if (parts == null) {
throw new IllegalArgumentException("Parts may not be null");
}
long total = 0;
for (int i = 0; i < parts.length; i++) {
+ // set the part boundary before we calculate the part's length
+ parts[i].setPartBoundary(partBoundary);
total += parts[i].length();
}
total += EXTRA_BYTES.length;
- total += BOUNDARY_BYTES.length;
+ total += partBoundary.length;
total += EXTRA_BYTES.length;
total += CRLF_BYTES.length;
return total;
Index: java/org/apache/commons/httpclient/params/HttpMethodParams.java
===================================================================
RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/params/HttpMethodParams.java,v
retrieving revision 1.15
diff -u -r1.15 HttpMethodParams.java
--- java/org/apache/commons/httpclient/params/HttpMethodParams.java 17 Sep 2004 08:00:51 -0000 1.15
+++ java/org/apache/commons/httpclient/params/HttpMethodParams.java 29 Sep 2004 02:40:31 -0000
@@ -258,6 +258,14 @@
public static final String BUFFER_WARN_TRIGGER_LIMIT = "http.method.response.buffer.warnlimit";
/**
+ * Sets the value to use as the multipart boundary.
+ *
+ * This parameter expects a value if type {@link String}. + *
+ */ + public static final String MULTIPART_BOUNDARY = "http.method.multipart.boundary"; + + /** * Creates a new collection of parameters with the collection returned * by {@link #getDefaultParams()} as a parent. The collection will defer * to its parent for a default value if a particular parameter is not Index: test/org/apache/commons/httpclient/TestEffectiveHttpVersion.java =================================================================== RCS file: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestEffectiveHttpVersion.java,v retrieving revision 1.2 diff -u -r1.2 TestEffectiveHttpVersion.java --- test/org/apache/commons/httpclient/TestEffectiveHttpVersion.java 14 Sep 2004 15:50:40 -0000 1.2 +++ test/org/apache/commons/httpclient/TestEffectiveHttpVersion.java 29 Sep 2004 02:40:32 -0000 @@ -36,9 +36,6 @@ import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.httpclient.params.HttpMethodParams; -import org.apache.commons.httpclient.server.HttpService; -import org.apache.commons.httpclient.server.SimpleRequest; -import org.apache.commons.httpclient.server.SimpleResponse; /** * HTTP protocol versioning tests. @@ -64,22 +61,6 @@ public static Test suite() { return new TestSuite(TestEffectiveHttpVersion.class); - } - - private class EchoService implements HttpService { - - public EchoService() { - super(); - } - - public boolean process(final SimpleRequest request, final SimpleResponse response) - throws IOException - { - HttpVersion httpversion = request.getRequestLine().getHttpVersion(); - response.setStatusLine(httpversion, HttpStatus.SC_OK); - response.setBodyString(request.getBodyString()); - return true; - } } public void testClientLevelHttpVersion() throws IOException { Index: test/org/apache/commons/httpclient/TestNoHost.java =================================================================== RCS file: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestNoHost.java,v retrieving revision 1.40 diff -u -r1.40 TestNoHost.java --- test/org/apache/commons/httpclient/TestNoHost.java 28 Sep 2004 21:08:48 -0000 1.40 +++ test/org/apache/commons/httpclient/TestNoHost.java 29 Sep 2004 02:40:32 -0000 @@ -91,6 +91,7 @@ suite.addTestSuite(TestIdleConnectionTimeout.class); suite.addTest(TestMethodAbort.suite()); suite.addTest(TestHttpParams.suite()); + suite.addTest(TestMultipartPost.suite()); return suite; } Index: test/org/apache/commons/httpclient/TestWebapp.java =================================================================== RCS file: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestWebapp.java,v retrieving revision 1.10 diff -u -r1.10 TestWebapp.java --- test/org/apache/commons/httpclient/TestWebapp.java 25 Apr 2004 12:25:09 -0000 1.10 +++ test/org/apache/commons/httpclient/TestWebapp.java 29 Sep 2004 02:40:32 -0000 @@ -67,7 +67,6 @@ suite.addTest(TestWebappRedirect.suite()); suite.addTest(TestWebappBasicAuth.suite()); suite.addTest(TestWebappPostMethod.suite()); - suite.addTest(TestWebappMultiPostMethod.suite()); suite.addTest(TestWebappNoncompliant.suite()); suite.addTest(TestProxy.suite()); return suite; Index: test/org/apache/commons/httpclient/TestWebappMultiPostMethod.java =================================================================== RCS file: test/org/apache/commons/httpclient/TestWebappMultiPostMethod.java diff -N test/org/apache/commons/httpclient/TestWebappMultiPostMethod.java --- test/org/apache/commons/httpclient/TestWebappMultiPostMethod.java 22 Feb 2004 18:08:50 -0000 1.5 +++ /dev/null 1 Jan 1970 00:00:00 -0000 @@ -1,115 +0,0 @@ -/* - * $Header: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestWebappMultiPostMethod.java,v 1.5 2004/02/22 18:08:50 olegk Exp $ - * $Revision: 1.5 $ - * $Date: 2004/02/22 18:08:50 $ - * - * ==================================================================== - * - * Copyright 2003-2004 The Apache Software Foundation - * - * Licensed 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 - *+ * The HTTP multipart POST method is defined in section 3.3 of + * RFC1867: + *
+ * The media-type multipart/form-data follows the rules of all multipart + * MIME data streams as outlined in RFC 1521. The multipart/form-data contains + * a series of parts. Each part is expected to contain a content-disposition + * header where the value is "form-data" and a name attribute specifies + * the field name within the form, e.g., 'content-disposition: form-data; + * name="xxxxx"', where xxxxx is the field name corresponding to that field. + * Field names originally in non-ASCII character sets may be encoded using + * the method outlined in RFC 1522. + *+ * + * + * @since 3.0 + */ +public class MultipartRequestEntity implements RequestEntity { + + private static final Log log = LogFactory.getLog(MultipartRequestEntity.class); + + /** The Content-Type for multipart/form-data. */ + private static final String MULTIPART_FORM_CONTENT_TYPE = "multipart/form-data"; + + /** + * The pool of ASCII chars to be used for generating a multipart boundary. + */ + private static byte[] MULTIPART_CHARS = ("-_1234567890abcdefghijklmnopqrstuvwxyz" + + "ABCDEFGHIJKLMNOPQRSTUVWXYZ").getBytes(); + + /** + * Generates a random multipart boundary string. + * @return + */ + private static byte[] generateMultipartBoundary() { + Random rand = new Random(); + byte[] bytes = new byte[rand.nextInt(11) + 30]; // a random size from 30 to 40 + for (int i = 0; i < bytes.length; i++) { + bytes[i] = MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)]; + } + return bytes; + } + + private Part[] parts; + + private byte[] multipartBoundary; + + private HttpMethodParams params; + + /** + * + */ + public MultipartRequestEntity(Part[] parts, HttpMethodParams params) { + if (parts == null) { + throw new IllegalArgumentException("parts cannot be null"); + } + if (params == null) { + throw new IllegalArgumentException("params cannot be null"); + } + this.parts = parts; + this.params = params; + } + + private byte[] getMultipartBoundary() { + if (multipartBoundary == null) { + String temp = (String) params.getParameter(HttpMethodParams.MULTIPART_BOUNDARY); + if (temp != null) { + multipartBoundary = EncodingUtil.getAsciiBytes(temp); + } else { + multipartBoundary = generateMultipartBoundary(); + } + } + return multipartBoundary; + } + + /** + * Returns
true if all parts are repeatable, false otherwise.
+ * @see org.apache.commons.httpclient.methods.RequestEntity#isRepeatable()
+ */
+ public boolean isRepeatable() {
+ for (int i = 0; i < parts.length; i++) {
+ if (!parts[i].isRepeatable()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.commons.httpclient.methods.RequestEntity#writeRequest(java.io.OutputStream)
+ */
+ public void writeRequest(OutputStream out) throws IOException {
+ Part.sendParts(out, parts, getMultipartBoundary());
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.commons.httpclient.methods.RequestEntity#getContentLength()
+ */
+ public long getContentLength() {
+ try {
+ return Part.getLengthOfParts(parts, getMultipartBoundary());
+ } catch (Exception e) {
+ log.error("An exception occurred while getting the length of the parts", e);
+ return 0;
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.commons.httpclient.methods.RequestEntity#getContentType()
+ */
+ public String getContentType() {
+ StringBuffer buffer = new StringBuffer(MULTIPART_FORM_CONTENT_TYPE);
+ buffer.append("; boundary=");
+ buffer.append(EncodingUtil.getAsciiString(getMultipartBoundary()));
+ return buffer.toString();
+ }
+
+}
Index: test/org/apache/commons/httpclient/EchoService.java
===================================================================
RCS file: test/org/apache/commons/httpclient/EchoService.java
diff -N test/org/apache/commons/httpclient/EchoService.java
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ test/org/apache/commons/httpclient/EchoService.java 1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,61 @@
+/*
+ * $Header: $
+ * $Revision: $
+ * $Date: $
+ *
+ * ====================================================================
+ *
+ * Copyright 2002-2004 The Apache Software Foundation
+ *
+ * Licensed 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
+ * Transparently coalesces chunks of a HTTP stream that uses + * Transfer-Encoding chunked.
+ * + *Note that this class NEVER closes the underlying stream, even when close + * gets called. Instead, it will read until the "end" of its chunking on close, + * which allows for the seamless invocation of subsequent HTTP 1.1 calls, while + * not requiring the client to remember to read the entire contents of the + * response.
+ * + * @author Ortwin Gl?ck + * @author Sean C. Sullivan + * @author Martin Elwin + * @author Eric Johnson + * @author Mike Bowler + * @author Michael Becke + * @author Oleg Kalnichevski + * + * @since 2.0 + * + */ +public class SimpleChunkedInputStream extends InputStream { + /** The inputstream that we're wrapping */ + private InputStream in; + + /** The chunk size */ + private int chunkSize; + + /** 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; + + /** True if this stream is closed */ + private boolean closed = false; + + private String elementCharset = null; + + /** Log object for this class. */ + private static final Log LOG = LogFactory.getLog(ChunkedInputStream.class); + /** + * + * + * @param in must be non-null + * @param method must be non-null + * + * @throws IOException If an IO error occurs + */ + public SimpleChunkedInputStream( + final InputStream in, final String elementCharset) throws IOException { + + if (in == null) { + throw new IllegalArgumentException("InputStream parameter may not be null"); + } + this.elementCharset = elementCharset; + this.in = in; + this.pos = 0; + } + + /** + *Returns all the data in a chunked stream in coalesced form. A chunk + * is followed by a CRLF. The method returns -1 as soon as a chunksize of 0 + * is detected.
+ * + *Trailer headers are read automcatically at the end of the stream and + * can be obtained with the getResponseFooters() method.
+ * + * @return -1 of the end of the stream has been reached or the next data + * byte + * @throws IOException If an IO problem occurs + * + * @see HttpMethod#getResponseFooters() + */ + public int read() throws IOException { + + if (closed) { + throw new IOException("Attempted read from closed stream."); + } + if (eof) { + return -1; + } + if (pos >= chunkSize) { + nextChunk(); + if (eof) { + return -1; + } + } + pos++; + return in.read(); + } + + /** + * Read some bytes from the stream. + * @param b The byte array that will hold the contents from the stream. + * @param off The offset into the byte array at which bytes will start to be + * placed. + * @param len the maximum number of bytes that can be returned. + * @return The number of bytes returned or -1 if the end of stream has been + * reached. + * @see java.io.InputStream#read(byte[], int, int) + * @throws IOException if an IO problem occurs. + */ + public int read (byte[] b, int off, int len) throws IOException { + + if (closed) { + throw new IOException("Attempted read from closed stream."); + } + + if (eof) { + return -1; + } + if (pos >= chunkSize) { + nextChunk(); + if (eof) { + return -1; + } + } + len = Math.min(len, chunkSize - pos); + int count = in.read(b, off, len); + pos += count; + return count; + } + + /** + * Read some bytes from the stream. + * @param b The byte array that will hold the contents from the stream. + * @return The number of bytes returned or -1 if the end of stream has been + * reached. + * @see java.io.InputStream#read(byte[]) + * @throws IOException if an IO problem occurs. + */ + public int read (byte[] b) throws IOException { + return read(b, 0, b.length); + } + + /** + * Read the CRLF terminator. + * @throws IOException If an IO error occurs. + */ + 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; + parseTrailerHeaders(); + } + } + + /** + * 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. + * @param required true if a valid chunk must be present, + * false 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 { + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + // States: 0=normal, 1=\r was scanned, 2=inside quoted string, -1=end + int state = 0; + while (state != -1) { + int b = in.read(); + if (b == -1) { + throw new IOException("chunked stream ended unexpectedly"); + } + switch (state) { + case 0: + switch (b) { + case '\r': + state = 1; + break; + case '\"': + state = 2; + /* fall through */ + default: + baos.write(b); + } + break; + + case 1: + if (b == '\n') { + state = -1; + } else { + // this was not CRLF + throw new IOException("Protocol violation: Unexpected" + + " single newline character in chunk size"); + } + break; + + case 2: + switch (b) { + case '\\': + b = in.read(); + baos.write(b); + break; + case '\"': + state = 0; + /* fall through */ + default: + baos.write(b); + } + break; + default: throw new RuntimeException("assertion failed"); + } + } + + //parse data + String dataString = EncodingUtil.getAsciiString(baos.toByteArray()); + int separator = dataString.indexOf(';'); + dataString = (separator > 0) + ? dataString.substring(0, separator).trim() + : dataString.trim(); + + int result; + try { + result = Integer.parseInt(dataString.trim(), 16); + } catch (NumberFormatException e) { + throw new IOException ("Bad chunk size: " + dataString); + } + return result; + } + + /** + * Reads and stores the Trailer headers. + * @throws IOException If an IO problem occurs + */ + private void parseTrailerHeaders() throws IOException { + try { + // ignore trailer headers + while (true) { + String line = HttpParser.readLine(in, elementCharset); + if ((line == null) || (line.length() < 1)) { + break; + } + } + } catch(HttpException e) { + LOG.error("Error parsing trailer headers", e); + IOException ioe = new IOException(e.getMessage()); + ExceptionUtil.initCause(ioe, e); + throw ioe; + } + } + + /** + * Upon close, this reads the remainder of the chunked message, + * leaving the underlying socket at a position to start reading the + * next response without scanning. + * @throws IOException If an IO problem occurs. + */ + public void close() throws IOException { + if (!closed) { + try { + if (!eof) { + exhaustInputStream(this); + } + } finally { + eof = true; + closed = true; + } + } + } + + /** + * Exhaust an input stream, reading until EOF has been encountered. + * + *Note that this function is intended as a non-public utility. + * This is a little weird, but it seemed silly to make a utility + * class for this one function, so instead it is just static and + * shared that way.
+ * + * @param inStream The {@link InputStream} to exhaust. + * @throws IOException If an IO problem occurs + */ + static void exhaustInputStream(InputStream inStream) throws IOException { + // read and discard the remainder of the message + byte buffer[] = new byte[1024]; + while (inStream.read(buffer) >= 0) { + ; + } + } +} Index: test/org/apache/commons/httpclient/TestMultipartPost.java =================================================================== RCS file: test/org/apache/commons/httpclient/TestMultipartPost.java diff -N test/org/apache/commons/httpclient/TestMultipartPost.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ test/org/apache/commons/httpclient/TestMultipartPost.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,119 @@ +/* + * $Header: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestWebappMultiPostMethod.java,v 1.5 2004/02/22 18:08:50 olegk Exp $ + * $Revision: 1.5 $ + * $Date: 2004/02/22 18:08:50 $ + * + * ==================================================================== + * + * Copyright 2003-2004 The Apache Software Foundation + * + * Licensed 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 + *