From 275d00cb283fcbadec51b099a677e46f05d8bebd Mon Sep 17 00:00:00 2001 From: Oleg Kalnichevski Date: Wed, 30 Jan 2008 11:32:57 +0000 Subject: [PATCH] MIME multipart/form-data: strict (RFC 822, RFC 2045, RFC 2046 compliant) and lenient (browser compatible) modes git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@616725 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/http/client/mime/FormBodyPart.java | 2 +- .../http/client/mime/HttpMultipart.java | 101 +++++++++++++++--- .../http/client/mime/HttpMultipartMode.java | 39 +++++++ .../org/apache/http/client/mime/MIME.java | 5 + .../apache/http/client/mime/RFC822Header.java | 56 ++++++++++ .../http/client/mime/content/FileBody.java | 9 +- .../client/mime/content/InputStreamBody.java | 9 +- .../http/client/mime/content/StringBody.java | 3 +- .../http/client/mime/TestMultipartForm.java | 56 ++++++++++ 9 files changed, 251 insertions(+), 29 deletions(-) create mode 100644 module-httpmime/src/main/java/org/apache/http/client/mime/HttpMultipartMode.java create mode 100644 module-httpmime/src/main/java/org/apache/http/client/mime/RFC822Header.java diff --git a/module-httpmime/src/main/java/org/apache/http/client/mime/FormBodyPart.java b/module-httpmime/src/main/java/org/apache/http/client/mime/FormBodyPart.java index 39d0e52f8..ee8409c93 100644 --- a/module-httpmime/src/main/java/org/apache/http/client/mime/FormBodyPart.java +++ b/module-httpmime/src/main/java/org/apache/http/client/mime/FormBodyPart.java @@ -50,7 +50,7 @@ public class FormBodyPart extends BodyPart { } this.name = name; - Header header = new Header(); + Header header = new RFC822Header(); setHeader(header); setBody(body); diff --git a/module-httpmime/src/main/java/org/apache/http/client/mime/HttpMultipart.java b/module-httpmime/src/main/java/org/apache/http/client/mime/HttpMultipart.java index a8b448530..e71765f3d 100644 --- a/module-httpmime/src/main/java/org/apache/http/client/mime/HttpMultipart.java +++ b/module-httpmime/src/main/java/org/apache/http/client/mime/HttpMultipart.java @@ -1,7 +1,7 @@ /* - * $HeadURL:$ - * $Revision:$ - * $Date:$ + * $HeadURL$ + * $Revision$ + * $Date$ * * ==================================================================== * Licensed to the Apache Software Foundation (ASF) under one @@ -35,8 +35,10 @@ import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; +import java.nio.charset.Charset; import java.util.List; +import org.apache.http.protocol.HTTP; import org.apache.james.mime4j.field.ContentTypeField; import org.apache.james.mime4j.field.Field; import org.apache.james.mime4j.message.BodyPart; @@ -51,38 +53,103 @@ import org.apache.james.mime4j.util.CharsetUtil; */ public class HttpMultipart extends Multipart { + private HttpMultipartMode mode; + + public HttpMultipart() { + super(); + this.mode = HttpMultipartMode.STRICT; + } + + public HttpMultipartMode getMode() { + return this.mode; + } + + public void setMode(final HttpMultipartMode mode) { + this.mode = mode; + } + @Override public void writeTo(OutputStream out) throws IOException { Entity e = getParent(); + ContentTypeField cField = (ContentTypeField) e.getHeader().getField( Field.CONTENT_TYPE); String boundary = cField.getBoundary(); - String charset = cField.getCharset(); + Charset charset = null; + + switch (this.mode) { + case STRICT: + charset = MIME.DEFAULT_CHARSET; + break; + case BROWSER_COMPATIBLE: + if (cField.getCharset() != null) { + charset = CharsetUtil.getCharset(cField.getCharset()); + } else { + charset = CharsetUtil.getCharset(HTTP.DEFAULT_CONTENT_CHARSET); + } + break; + } List bodyParts = getBodyParts(); BufferedWriter writer = new BufferedWriter( - new OutputStreamWriter(out, CharsetUtil.getCharset(charset)), + new OutputStreamWriter(out, charset), 8192); - writer.write(getPreamble()); - writer.write("\r\n"); + switch (this.mode) { + case STRICT: + writer.write(getPreamble()); + writer.write("\r\n"); + + for (int i = 0; i < bodyParts.size(); i++) { + writer.write("--"); + writer.write(boundary); + writer.write("\r\n"); + writer.flush(); + BodyPart part = (BodyPart) bodyParts.get(i); + part.writeTo(out); + writer.write("\r\n"); + } - for (int i = 0; i < bodyParts.size(); i++) { writer.write("--"); writer.write(boundary); + writer.write("--\r\n"); + writer.write(getEpilogue()); writer.write("\r\n"); writer.flush(); - ((BodyPart) bodyParts.get(i)).writeTo(out); - writer.write("\r\n"); - } + break; + case BROWSER_COMPATIBLE: - writer.write("--"); - writer.write(boundary); - writer.write("--\r\n"); - writer.write(getEpilogue()); - writer.write("\r\n"); - writer.flush(); + // (1) Do not write preamble and epilogue + // (2) Only write Content-Disposition + // (3) Use content charset + + writer.write("\r\n"); + + for (int i = 0; i < bodyParts.size(); i++) { + writer.write("--"); + writer.write(boundary); + writer.write("\r\n"); + writer.flush(); + BodyPart part = (BodyPart) bodyParts.get(i); + + Field cd = part.getHeader().getField(MIME.CONTENT_DISPOSITION); + writer.write(cd.toString()); + writer.write("\r\n"); + writer.write("\r\n"); + writer.flush(); + part.getBody().writeTo(out); + + writer.write("\r\n"); + } + + writer.write("--"); + writer.write(boundary); + writer.write("--\r\n"); + writer.write("\r\n"); + writer.flush(); + break; + } } } diff --git a/module-httpmime/src/main/java/org/apache/http/client/mime/HttpMultipartMode.java b/module-httpmime/src/main/java/org/apache/http/client/mime/HttpMultipartMode.java new file mode 100644 index 000000000..14085aa36 --- /dev/null +++ b/module-httpmime/src/main/java/org/apache/http/client/mime/HttpMultipartMode.java @@ -0,0 +1,39 @@ +/* + * $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; + +public enum HttpMultipartMode { + + STRICT, + BROWSER_COMPATIBLE + +} diff --git a/module-httpmime/src/main/java/org/apache/http/client/mime/MIME.java b/module-httpmime/src/main/java/org/apache/http/client/mime/MIME.java index 9903dd48f..dddc8b828 100644 --- a/module-httpmime/src/main/java/org/apache/http/client/mime/MIME.java +++ b/module-httpmime/src/main/java/org/apache/http/client/mime/MIME.java @@ -31,7 +31,10 @@ package org.apache.http.client.mime; +import java.nio.charset.Charset; + import org.apache.james.mime4j.field.Field; +import org.apache.james.mime4j.util.CharsetUtil; public final class MIME { @@ -41,5 +44,7 @@ public final class MIME { public static final String ENC_8BIT = "8bit"; public static final String ENC_BINARY = "binary"; + + public static final Charset DEFAULT_CHARSET = CharsetUtil.getCharset("US-ASCII"); } diff --git a/module-httpmime/src/main/java/org/apache/http/client/mime/RFC822Header.java b/module-httpmime/src/main/java/org/apache/http/client/mime/RFC822Header.java new file mode 100644 index 000000000..fb0de3292 --- /dev/null +++ b/module-httpmime/src/main/java/org/apache/http/client/mime/RFC822Header.java @@ -0,0 +1,56 @@ +/* + * $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.BufferedWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.util.Iterator; + +import org.apache.james.mime4j.message.Header; + +class RFC822Header extends Header { + + @Override + public void writeTo(final OutputStream out) throws IOException { + BufferedWriter writer = new BufferedWriter( + new OutputStreamWriter(out, MIME.DEFAULT_CHARSET), 8192); + for (Iterator it = getFields().iterator(); it.hasNext();) { + writer.write(it.next().toString()); + writer.write("\r\n"); + } + writer.write("\r\n"); + writer.flush(); + } + +} diff --git a/module-httpmime/src/main/java/org/apache/http/client/mime/content/FileBody.java b/module-httpmime/src/main/java/org/apache/http/client/mime/content/FileBody.java index 1f5bf8b40..8f13863e9 100644 --- a/module-httpmime/src/main/java/org/apache/http/client/mime/content/FileBody.java +++ b/module-httpmime/src/main/java/org/apache/http/client/mime/content/FileBody.java @@ -35,16 +35,15 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; import java.io.OutputStream; -import java.io.Reader; import java.nio.charset.Charset; import org.apache.commons.io.IOUtils; import org.apache.http.client.mime.MIME; import org.apache.james.mime4j.message.AbstractBody; +import org.apache.james.mime4j.message.BinaryBody; -public class FileBody extends AbstractBody implements ContentBody { +public class FileBody extends AbstractBody implements BinaryBody, ContentBody { private final File file; @@ -56,8 +55,8 @@ public class FileBody extends AbstractBody implements ContentBody { this.file = file; } - public Reader getReader() throws IOException { - return new InputStreamReader(new FileInputStream(this.file)); + public InputStream getInputStream() throws IOException { + return new FileInputStream(this.file); } public void writeTo(final OutputStream out) throws IOException { diff --git a/module-httpmime/src/main/java/org/apache/http/client/mime/content/InputStreamBody.java b/module-httpmime/src/main/java/org/apache/http/client/mime/content/InputStreamBody.java index ebd91b9fe..6032475ae 100644 --- a/module-httpmime/src/main/java/org/apache/http/client/mime/content/InputStreamBody.java +++ b/module-httpmime/src/main/java/org/apache/http/client/mime/content/InputStreamBody.java @@ -33,16 +33,15 @@ package org.apache.http.client.mime.content; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; import java.io.OutputStream; -import java.io.Reader; import java.nio.charset.Charset; import org.apache.commons.io.IOUtils; import org.apache.http.client.mime.MIME; import org.apache.james.mime4j.message.AbstractBody; +import org.apache.james.mime4j.message.BinaryBody; -public class InputStreamBody extends AbstractBody implements ContentBody { +public class InputStreamBody extends AbstractBody implements BinaryBody, ContentBody { private final InputStream in; private final String filename; @@ -56,8 +55,8 @@ public class InputStreamBody extends AbstractBody implements ContentBody { this.filename = filename; } - public Reader getReader() throws IOException { - return new InputStreamReader(this.in); + public InputStream getInputStream() throws IOException { + return this.in; } public void writeTo(final OutputStream out) throws IOException { diff --git a/module-httpmime/src/main/java/org/apache/http/client/mime/content/StringBody.java b/module-httpmime/src/main/java/org/apache/http/client/mime/content/StringBody.java index 2aa200da3..7c83dc398 100644 --- a/module-httpmime/src/main/java/org/apache/http/client/mime/content/StringBody.java +++ b/module-httpmime/src/main/java/org/apache/http/client/mime/content/StringBody.java @@ -42,8 +42,9 @@ import java.nio.charset.Charset; import org.apache.commons.io.IOUtils; import org.apache.http.client.mime.MIME; import org.apache.james.mime4j.message.AbstractBody; +import org.apache.james.mime4j.message.TextBody; -public class StringBody extends AbstractBody implements ContentBody { +public class StringBody extends AbstractBody implements TextBody, ContentBody { private final byte[] content; private final Charset charset; diff --git a/module-httpmime/src/test/java/org/apache/http/client/mime/TestMultipartForm.java b/module-httpmime/src/test/java/org/apache/http/client/mime/TestMultipartForm.java index 86b9fd8f1..192635ee6 100644 --- a/module-httpmime/src/test/java/org/apache/http/client/mime/TestMultipartForm.java +++ b/module-httpmime/src/test/java/org/apache/http/client/mime/TestMultipartForm.java @@ -183,6 +183,7 @@ public class TestMultipartForm extends TestCase { message.setHeader(header); File tmpfile = File.createTempFile("tmp", ".bin"); + tmpfile.deleteOnExit(); Writer writer = new FileWriter(tmpfile); try { writer.append("some random whatever"); @@ -225,6 +226,61 @@ public class TestMultipartForm extends TestCase { "\r\n"; String s = out.toString("US-ASCII"); assertEquals(expected, s); + + tmpfile.delete(); + } + + public void testMultipartFormBrowserCompatible() throws Exception { + Message message = new Message(); + Header header = new Header(); + header.addField( + Field.parse("Content-Type: multipart/form-data; boundary=foo")); + message.setHeader(header); + + File tmpfile = File.createTempFile("tmp", ".bin"); + tmpfile.deleteOnExit(); + Writer writer = new FileWriter(tmpfile); + try { + writer.append("some random whatever"); + } finally { + writer.close(); + } + + HttpMultipart multipart = new HttpMultipart(); + multipart.setParent(message); + FormBodyPart p1 = new FormBodyPart( + "field1", + new FileBody(tmpfile)); + FormBodyPart p2 = new FormBodyPart( + "field2", + new InputStreamBody(new FileInputStream(tmpfile), "file.tmp")); + + multipart.addBodyPart(p1); + multipart.addBodyPart(p2); + + multipart.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + multipart.writeTo(out); + out.close(); + + String expected = "\r\n" + + "--foo\r\n" + + "Content-Disposition: form-data; name=\"field1\"; " + + "filename=\"" + tmpfile.getName() + "\"\r\n" + + "\r\n" + + "some random whatever\r\n" + + "--foo\r\n" + + "Content-Disposition: form-data; name=\"field2\"; " + + "filename=\"file.tmp\"\r\n" + + "\r\n" + + "some random whatever\r\n" + + "--foo--\r\n" + + "\r\n"; + String s = out.toString("US-ASCII"); + assertEquals(expected, s); + + tmpfile.delete(); } }