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); + } + +}