diff --git a/module-httpmime/src/main/java/org/apache/http/client/mime/MultipartFormHttpEntity.java b/module-httpmime/src/main/java/org/apache/http/client/mime/MultipartFormHttpEntity.java
new file mode 100644
index 000000000..86970bf64
--- /dev/null
+++ b/module-httpmime/src/main/java/org/apache/http/client/mime/MultipartFormHttpEntity.java
@@ -0,0 +1,181 @@
+/*
+ * $HeadURL$
+ * $Revision$
+ * $Date$
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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
+ * .
+ *
+ */
+
+package org.apache.http.client.mime;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Random;
+
+import org.apache.http.Header;
+import org.apache.http.HttpEntity;
+import org.apache.http.client.mime.content.ContentBody;
+import org.apache.http.message.BasicHeader;
+import org.apache.http.protocol.HTTP;
+import org.apache.james.mime4j.field.Field;
+import org.apache.james.mime4j.message.Message;
+
+/**
+ * Multipart/form coded HTTP entity consisting of multiple
+ * body parts.
+ *
+ * @author Oleg Kalnichevski
+ */
+public class MultipartFormHttpEntity implements HttpEntity {
+
+ /**
+ * The pool of ASCII chars to be used for generating a multipart boundary.
+ */
+ private final static char[] MULTIPART_CHARS =
+ "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ .toCharArray();
+
+ private final HttpMultipart multipart;
+ private final Header contentType;
+
+ private long length;
+ private boolean dirty;
+
+ public MultipartFormHttpEntity(
+ HttpMultipartMode mode,
+ final String boundary,
+ final Charset charset) {
+ super();
+ this.multipart = new HttpMultipart();
+ this.contentType = new BasicHeader(
+ HTTP.CONTENT_TYPE,
+ generateContentType(boundary, charset));
+ this.dirty = true;
+
+ Message message = new Message();
+ org.apache.james.mime4j.message.Header header = new RFC822Header();
+ header.addField(
+ Field.parse("Content-Type: " + this.contentType.getValue()));
+ message.setHeader(header);
+ this.multipart.setParent(message);
+ if (mode == null) {
+ mode = HttpMultipartMode.STRICT;
+ }
+ this.multipart.setMode(mode);
+ }
+
+ public MultipartFormHttpEntity(final HttpMultipartMode mode) {
+ this(mode, null, null);
+ }
+
+ public MultipartFormHttpEntity() {
+ this(HttpMultipartMode.STRICT, null, null);
+ }
+
+ protected String generateContentType(
+ final String boundary,
+ final Charset charset) {
+ StringBuilder buffer = new StringBuilder();
+ buffer.append("multipart/form-data; boundary=");
+ if (boundary != null) {
+ buffer.append(boundary);
+ } else {
+ Random rand = new Random();
+ int count = rand.nextInt(11) + 30; // a random size from 30 to 40
+ for (int i = 0; i < count; i++) {
+ buffer.append(MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)]);
+ }
+ }
+ if (charset != null) {
+ buffer.append("; charset=");
+ buffer.append(charset.name());
+ }
+ return buffer.toString();
+ }
+
+ public void addPart(final String name, final ContentBody contentBody) {
+ this.multipart.addBodyPart(new FormBodyPart(name, contentBody));
+ this.dirty = true;
+ }
+
+ public boolean isRepeatable() {
+ List> parts = this.multipart.getBodyParts();
+ for (Iterator> it = parts.iterator(); it.hasNext(); ) {
+ FormBodyPart part = (FormBodyPart) it.next();
+ ContentBody body = (ContentBody) part.getBody();
+ if (body.getContentLength() < 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public boolean isChunked() {
+ return !isRepeatable();
+ }
+
+ public boolean isStreaming() {
+ return !isRepeatable();
+ }
+
+ public long getContentLength() {
+ if (this.dirty) {
+ this.length = this.multipart.getTotalLength();
+ this.dirty = false;
+ }
+ return this.length;
+ }
+
+ public Header getContentType() {
+ return this.contentType;
+ }
+
+ public Header getContentEncoding() {
+ return null;
+ }
+
+ public void consumeContent()
+ throws IOException, UnsupportedOperationException{
+ if (isStreaming()) {
+ throw new UnsupportedOperationException(
+ "Streaming entity does not implement #consumeContent()");
+ }
+ }
+
+ public InputStream getContent() throws IOException, UnsupportedOperationException {
+ throw new UnsupportedOperationException(
+ "Multipart form entity does not implement #getContent()");
+ }
+
+ public void writeTo(final OutputStream outstream) throws IOException {
+ this.multipart.writeTo(outstream);
+ }
+
+}
diff --git a/module-httpmime/src/test/java/org/apache/http/client/mime/TestAll.java b/module-httpmime/src/test/java/org/apache/http/client/mime/TestAll.java
index 142075dd2..da7c4fab0 100644
--- a/module-httpmime/src/test/java/org/apache/http/client/mime/TestAll.java
+++ b/module-httpmime/src/test/java/org/apache/http/client/mime/TestAll.java
@@ -1,7 +1,7 @@
/*
- * $HeadURL:$
- * $Revision:$
- * $Date:$
+ * $HeadURL$
+ * $Revision$
+ * $Date$
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
@@ -43,6 +43,7 @@ public class TestAll extends TestCase {
public static Test suite() {
TestSuite suite = new TestSuite();
suite.addTest(TestMultipartForm.suite());
+ suite.addTest(TestMultipartFormHttpEntity.suite());
return suite;
}
diff --git a/module-httpmime/src/test/java/org/apache/http/client/mime/TestMultipartFormHttpEntity.java b/module-httpmime/src/test/java/org/apache/http/client/mime/TestMultipartFormHttpEntity.java
new file mode 100644
index 000000000..c82faadce
--- /dev/null
+++ b/module-httpmime/src/test/java/org/apache/http/client/mime/TestMultipartFormHttpEntity.java
@@ -0,0 +1,158 @@
+/*
+ * $HeadURL$
+ * $Revision$
+ * $Date$
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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
+ * .
+ *
+ */
+
+package org.apache.http.client.mime;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import org.apache.http.Header;
+import org.apache.http.HeaderElement;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.mime.content.InputStreamBody;
+import org.apache.http.client.mime.content.StringBody;
+import org.apache.james.mime4j.util.CharsetUtil;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+public class TestMultipartFormHttpEntity extends TestCase {
+
+ // ------------------------------------------------------------ Constructor
+ public TestMultipartFormHttpEntity(final String testName) throws IOException {
+ super(testName);
+ }
+
+ // ------------------------------------------------------------------- Main
+ public static void main(String args[]) {
+ String[] testCaseName = { TestMultipartFormHttpEntity.class.getName() };
+ junit.textui.TestRunner.main(testCaseName);
+ }
+
+ // ------------------------------------------------------- TestCase Methods
+
+ public static Test suite() {
+ return new TestSuite(TestMultipartFormHttpEntity.class);
+ }
+
+ public void testExplictContractorParams() throws Exception {
+ MultipartFormHttpEntity entity = new MultipartFormHttpEntity(
+ HttpMultipartMode.BROWSER_COMPATIBLE,
+ "whatever",
+ CharsetUtil.getCharset("UTF-8"));
+
+ assertNull(entity.getContentEncoding());
+ assertNotNull(entity.getContentType());
+ Header header = entity.getContentType();
+ HeaderElement[] elems = header.getElements();
+ assertNotNull(elems);
+ assertEquals(1, elems.length);
+
+ HeaderElement elem = elems[0];
+ assertEquals("multipart/form-data", elem.getName());
+ NameValuePair p1 = elem.getParameterByName("boundary");
+ assertNotNull(p1);
+ assertEquals("whatever", p1.getValue());
+ NameValuePair p2 = elem.getParameterByName("charset");
+ assertNotNull(p2);
+ assertEquals("UTF-8", p2.getValue());
+ }
+
+ public void testImplictContractorParams() throws Exception {
+ MultipartFormHttpEntity entity = new MultipartFormHttpEntity();
+ assertNull(entity.getContentEncoding());
+ assertNotNull(entity.getContentType());
+ Header header = entity.getContentType();
+ HeaderElement[] elems = header.getElements();
+ assertNotNull(elems);
+ assertEquals(1, elems.length);
+
+ HeaderElement elem = elems[0];
+ assertEquals("multipart/form-data", elem.getName());
+ NameValuePair p1 = elem.getParameterByName("boundary");
+ assertNotNull(p1);
+
+ String boundary = p1.getValue();
+ assertNotNull(boundary);
+
+ assertTrue(boundary.length() > 30);
+ assertTrue(boundary.length() <= 40);
+
+ NameValuePair p2 = elem.getParameterByName("charset");
+ assertNull(p2);
+ }
+
+ public void testRepeatable() throws Exception {
+ MultipartFormHttpEntity entity = new MultipartFormHttpEntity();
+ entity.addPart("p1", new StringBody("blah blah"));
+ entity.addPart("p2", new StringBody("yada yada"));
+ assertTrue(entity.isRepeatable());
+ assertFalse(entity.isChunked());
+ assertFalse(entity.isStreaming());
+
+ long len = entity.getContentLength();
+ assertTrue(len == entity.getContentLength());
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ entity.writeTo(out);
+ out.close();
+
+ byte[] bytes = out.toByteArray();
+ assertNotNull(bytes);
+ assertTrue(bytes.length == len);
+
+ assertTrue(len == entity.getContentLength());
+
+ out = new ByteArrayOutputStream();
+ entity.writeTo(out);
+ out.close();
+
+ bytes = out.toByteArray();
+ assertNotNull(bytes);
+ assertTrue(bytes.length == len);
+ }
+
+ public void testNonRepeatable() throws Exception {
+ MultipartFormHttpEntity entity = new MultipartFormHttpEntity();
+ entity.addPart("p1", new InputStreamBody(
+ new ByteArrayInputStream("blah blah".getBytes()), null));
+ entity.addPart("p2", new InputStreamBody(
+ new ByteArrayInputStream("yada yada".getBytes()), null));
+ assertFalse(entity.isRepeatable());
+ assertTrue(entity.isChunked());
+ assertTrue(entity.isStreaming());
+
+ assertTrue(entity.getContentLength() == -1);
+ }
+
+}