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 23 Sep 2004 02:45:38 -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 23 Sep 2004 02:45:39 -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 23 Sep 2004 02:45:40 -0000
@@ -55,7 +55,10 @@
//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 */
@@ -112,6 +115,7 @@
/**
* Return the boundary string.
* @return the boundary string
+ * @deprecated use {@link org.apache.commons.httpclient.params.HttpMethodParams#MULTIPART_BOUNDARY}
*/
public static String getBoundary() {
return BOUNDARY;
@@ -142,6 +146,16 @@
*/
public abstract String getTransferEncoding();
+ /**
+ * 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
Index: java/org/apache/commons/httpclient/params/DefaultHttpParamsFactory.java
===================================================================
RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/params/DefaultHttpParamsFactory.java,v
retrieving revision 1.13
diff -u -r1.13 DefaultHttpParamsFactory.java
--- java/org/apache/commons/httpclient/params/DefaultHttpParamsFactory.java 19 Sep 2004 19:37:07 -0000 1.13
+++ java/org/apache/commons/httpclient/params/DefaultHttpParamsFactory.java 23 Sep 2004 02:45:40 -0000
@@ -75,6 +75,7 @@
params.setHttpElementCharset("US-ASCII");
params.setContentCharset("ISO-8859-1");
params.setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler());
+ params.setParameter(HttpMethodParams.MULTIPART_BOUNDARY, "----------------314159265358979323846");
ArrayList datePatterns = new ArrayList();
datePatterns.addAll(
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 23 Sep 2004 02:45:42 -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 23 Sep 2004 02:45:42 -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.39 diff -u -r1.39 TestNoHost.java --- test/org/apache/commons/httpclient/TestNoHost.java 15 Sep 2004 20:42:17 -0000 1.39 +++ test/org/apache/commons/httpclient/TestNoHost.java 23 Sep 2004 02:45:42 -0000 @@ -92,6 +92,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 23 Sep 2004 02:45:43 -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"; + + private Part[] parts; + + 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; + } + + /** + * 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);
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.commons.httpclient.methods.RequestEntity#getContentLength()
+ */
+ public long getContentLength() {
+ try {
+ return Part.getLengthOfParts(parts);
+ } 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);
+ String boundary = (String) params.getParameter(HttpMethodParams.MULTIPART_BOUNDARY);
+ if (boundary != null) {
+ buffer.append("; boundary=");
+ buffer.append(boundary);
+ }
+ 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 + *