Skip to content

Commit f3f6911

Browse files
committed
Fix multipart/form-data, close AsyncHttpClient#1119, close AsyncHttpClient#1120, close AsyncHttpClient#1121
1 parent 5ea2cc8 commit f3f6911

File tree

12 files changed

+1961
-76
lines changed

12 files changed

+1961
-76
lines changed

client/src/main/java/org/asynchttpclient/request/body/multipart/ByteArrayPart.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,8 @@ public ByteArrayPart(String name, byte[] bytes, String contentType, Charset char
4141
}
4242

4343
public ByteArrayPart(String name, byte[] bytes, String contentType, Charset charset, String fileName, String contentId, String transferEncoding) {
44-
super(name, contentType, charset, contentId, transferEncoding);
44+
super(name, contentType, charset, fileName, contentId, transferEncoding);
4545
this.bytes = assertNotNull(bytes, "bytes");
46-
setFileName(fileName);
4746
}
4847

4948
public byte[] getBytes() {

client/src/main/java/org/asynchttpclient/request/body/multipart/FileLikePart.java

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,40 +13,58 @@
1313
package org.asynchttpclient.request.body.multipart;
1414

1515
import static org.asynchttpclient.util.MiscUtils.withDefault;
16-
import static io.netty.handler.codec.http.HttpHeaders.Values.*;
1716

17+
import java.io.IOException;
18+
import java.io.InputStream;
1819
import java.nio.charset.Charset;
1920

21+
import javax.activation.MimetypesFileTypeMap;
22+
2023
/**
2124
* This class is an adaptation of the Apache HttpClient implementation
2225
*/
2326
public abstract class FileLikePart extends PartBase {
2427

28+
private static final MimetypesFileTypeMap MIME_TYPES_FILE_TYPE_MAP;
29+
30+
static {
31+
try (InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("ahc-mime.types")) {
32+
MIME_TYPES_FILE_TYPE_MAP = new MimetypesFileTypeMap(is);
33+
} catch (IOException e) {
34+
throw new ExceptionInInitializerError(e);
35+
}
36+
}
37+
2538
/**
2639
* Default content encoding of file attachments.
2740
*/
28-
public static final String DEFAULT_CONTENT_TYPE = "application/octet-stream";
29-
3041
private String fileName;
3142

43+
private static String computeContentType(String contentType, String fileName) {
44+
if (contentType == null) {
45+
// TODO use a ThreadLocal to get work around synchronized?
46+
contentType = MIME_TYPES_FILE_TYPE_MAP.getContentType(withDefault(fileName, ""));
47+
}
48+
49+
return contentType;
50+
}
51+
3252
/**
3353
* FilePart Constructor.
3454
*
3555
* @param name the name for this part
36-
* @param contentType the content type for this part, if <code>null</code> the {@link #DEFAULT_CONTENT_TYPE default} is used
56+
* @param contentType the content type for this part, if <code>null</code> try to figure out from the fileName mime type
3757
* @param charset the charset encoding for this part
58+
* @param fileName the fileName
3859
* @param contentId the content id
3960
* @param transfertEncoding the transfer encoding
4061
*/
41-
public FileLikePart(String name, String contentType, Charset charset, String contentId, String transfertEncoding) {
62+
public FileLikePart(String name, String contentType, Charset charset, String fileName, String contentId, String transfertEncoding) {
4263
super(name,//
43-
withDefault(contentType, DEFAULT_CONTENT_TYPE),//
64+
computeContentType(contentType, fileName),//
4465
charset,//
4566
contentId,//
46-
withDefault(transfertEncoding, BINARY));
47-
}
48-
49-
public final void setFileName(String fileName) {
67+
transfertEncoding);
5068
this.fileName = fileName;
5169
}
5270

client/src/main/java/org/asynchttpclient/request/body/multipart/FilePart.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,17 @@ public FilePart(String name, File file, String contentType, Charset charset, Str
4242
}
4343

4444
public FilePart(String name, File file, String contentType, Charset charset, String fileName, String contentId, String transferEncoding) {
45-
super(name, contentType, charset, contentId, transferEncoding);
45+
super(name,//
46+
contentType,//
47+
charset,//
48+
fileName != null ? fileName : file.getName(),//
49+
contentId,//
50+
transferEncoding);
4651
if (!assertNotNull(file, "file").isFile())
4752
throw new IllegalArgumentException("File is not a normal file " + file.getAbsolutePath());
4853
if (!file.canRead())
4954
throw new IllegalArgumentException("File is not readable " + file.getAbsolutePath());
5055
this.file = file;
51-
setFileName(fileName != null ? fileName : file.getName());
5256
}
5357

5458
public File getFile() {

client/src/main/java/org/asynchttpclient/request/body/multipart/MultipartUtils.java

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
*/
1414
package org.asynchttpclient.request.body.multipart;
1515

16+
import static io.netty.handler.codec.http.HttpHeaders.Values.MULTIPART_FORM_DATA;
1617
import static java.nio.charset.StandardCharsets.US_ASCII;
17-
import static io.netty.handler.codec.http.HttpHeaders.Values.*;
1818
import static org.asynchttpclient.util.Assertions.assertNotNull;
1919
import static org.asynchttpclient.util.MiscUtils.isNonEmpty;
2020
import io.netty.handler.codec.http.HttpHeaders;
@@ -27,6 +27,7 @@
2727
import org.asynchttpclient.request.body.multipart.part.FileMultipartPart;
2828
import org.asynchttpclient.request.body.multipart.part.MessageEndMultipartPart;
2929
import org.asynchttpclient.request.body.multipart.part.MultipartPart;
30+
import org.asynchttpclient.request.body.multipart.part.StringMultipartPart;
3031
import org.asynchttpclient.util.StringUtils;
3132

3233
public class MultipartUtils {
@@ -81,19 +82,7 @@ public static List<MultipartPart<? extends Part>> generateMultipartParts(List<Pa
8182
multipartParts.add(new ByteArrayMultipartPart((ByteArrayPart) part, boundary));
8283

8384
} else if (part instanceof StringPart) {
84-
// convert to a byte array
85-
StringPart stringPart = (StringPart) part;
86-
byte[] bytes = stringPart.getValue().getBytes(stringPart.getCharset());
87-
ByteArrayPart byteArrayPart = new ByteArrayPart(//
88-
stringPart.getName(),//
89-
bytes, //
90-
stringPart.getContentType(), //
91-
stringPart.getCharset(), //
92-
null, //
93-
stringPart.getContentId(), //
94-
stringPart.getTransferEncoding());
95-
byteArrayPart.setCustomHeaders(stringPart.getCustomHeaders());
96-
multipartParts.add(new ByteArrayMultipartPart(byteArrayPart, boundary));
85+
multipartParts.add(new StringMultipartPart((StringPart) part, boundary));
9786

9887
} else {
9988
throw new IllegalArgumentException("Unknown part type: " + part);

client/src/main/java/org/asynchttpclient/request/body/multipart/StringPart.java

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,11 @@
2020

2121
public class StringPart extends PartBase {
2222

23-
/**
24-
* Default content encoding of string parameters.
25-
*/
26-
public static final String DEFAULT_CONTENT_TYPE = "text/plain";
27-
2823
/**
2924
* Default charset of string parameters
3025
*/
3126
public static final Charset DEFAULT_CHARSET = UTF_8;
3227

33-
/**
34-
* Default transfer encoding of string parameters
35-
*/
36-
public static final String DEFAULT_TRANSFER_ENCODING = "8bit";
37-
3828
/**
3929
* Contents of this StringPart.
4030
*/
@@ -44,14 +34,6 @@ private static Charset charsetOrDefault(Charset charset) {
4434
return withDefault(charset, DEFAULT_CHARSET);
4535
}
4636

47-
private static String contentTypeOrDefault(String contentType) {
48-
return withDefault(contentType, DEFAULT_CONTENT_TYPE);
49-
}
50-
51-
private static String transferEncodingOrDefault(String transferEncoding) {
52-
return withDefault(transferEncoding, DEFAULT_TRANSFER_ENCODING);
53-
}
54-
5537
public StringPart(String name, String value) {
5638
this(name, value, null);
5739
}
@@ -69,7 +51,7 @@ public StringPart(String name, String value, String contentType, Charset charset
6951
}
7052

7153
public StringPart(String name, String value, String contentType, Charset charset, String contentId, String transferEncoding) {
72-
super(name, contentTypeOrDefault(contentType), charsetOrDefault(charset), contentId, transferEncodingOrDefault(transferEncoding));
54+
super(name, contentType, charsetOrDefault(charset), contentId, transferEncoding);
7355
assertNotNull(value, "value");
7456

7557
if (value.indexOf(0) != -1)

client/src/main/java/org/asynchttpclient/request/body/multipart/part/ByteArrayMultipartPart.java

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,9 @@
2121

2222
import org.asynchttpclient.request.body.multipart.ByteArrayPart;
2323

24-
public class ByteArrayMultipartPart extends MultipartPart<ByteArrayPart> {
24+
public class ByteArrayMultipartPart extends FileLikeMultipartPart<ByteArrayPart> {
2525

26-
// lazy
27-
private ByteBuf contentBuffer;
26+
private final ByteBuf contentBuffer;
2827

2928
public ByteArrayMultipartPart(ByteArrayPart part, byte[] boundary) {
3029
super(part, boundary);
@@ -38,18 +37,12 @@ protected long getContentLength() {
3837

3938
@Override
4039
protected long transferContentTo(ByteBuf target) throws IOException {
41-
return transfer(lazyLoadContentBuffer(), target, MultipartState.POST_CONTENT);
40+
return transfer(contentBuffer, target, MultipartState.POST_CONTENT);
4241
}
4342

4443
@Override
4544
protected long transferContentTo(WritableByteChannel target) throws IOException {
46-
return transfer(lazyLoadContentBuffer(), target, MultipartState.POST_CONTENT);
47-
}
48-
49-
private ByteBuf lazyLoadContentBuffer() {
50-
if (contentBuffer == null)
51-
contentBuffer = Unpooled.wrappedBuffer(part.getBytes());
52-
return contentBuffer;
45+
return transfer(contentBuffer, target, MultipartState.POST_CONTENT);
5346
}
5447

5548
@Override
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package org.asynchttpclient.request.body.multipart.part;
2+
3+
import static java.nio.charset.StandardCharsets.US_ASCII;
4+
5+
import org.asynchttpclient.request.body.multipart.FileLikePart;
6+
7+
public abstract class FileLikeMultipartPart<T extends FileLikePart> extends MultipartPart<T> {
8+
9+
/**
10+
* Attachment's file name as a byte array
11+
*/
12+
private static final byte[] FILE_NAME_BYTES = "; filename=".getBytes(US_ASCII);
13+
14+
public FileLikeMultipartPart(T part, byte[] boundary) {
15+
super(part, boundary);
16+
}
17+
18+
protected void visitDispositionHeader(PartVisitor visitor) {
19+
super.visitDispositionHeader(visitor);
20+
if (part.getFileName() != null) {
21+
visitor.withBytes(FILE_NAME_BYTES);
22+
visitor.withByte(QUOTE_BYTE);
23+
visitor.withBytes(part.getFileName().getBytes(part.getCharset() != null ? part.getCharset() : US_ASCII));
24+
visitor.withByte(QUOTE_BYTE);
25+
}
26+
}
27+
}

client/src/main/java/org/asynchttpclient/request/body/multipart/part/FileMultipartPart.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
import org.asynchttpclient.netty.request.body.BodyChunkedInput;
2626
import org.asynchttpclient.request.body.multipart.FilePart;
2727

28-
public class FileMultipartPart extends MultipartPart<FilePart> {
28+
public class FileMultipartPart extends FileLikeMultipartPart<FilePart> {
2929

3030
private final FileChannel channel;
3131
private final long length;

client/src/main/java/org/asynchttpclient/request/body/multipart/part/MultipartPart.java

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@
2626
import java.nio.charset.Charset;
2727

2828
import org.asynchttpclient.Param;
29-
import org.asynchttpclient.request.body.multipart.FileLikePart;
29+
import org.asynchttpclient.request.body.multipart.PartBase;
3030
import org.asynchttpclient.request.body.multipart.part.PartVisitor.ByteBufVisitor;
3131
import org.asynchttpclient.request.body.multipart.part.PartVisitor.CounterPartVisitor;
3232

33-
public abstract class MultipartPart<T extends FileLikePart> implements Closeable {
33+
public abstract class MultipartPart<T extends PartBase> implements Closeable {
3434

3535
/**
3636
* Carriage return/linefeed as a byte array
@@ -40,7 +40,7 @@ public abstract class MultipartPart<T extends FileLikePart> implements Closeable
4040
/**
4141
* Content disposition as a byte
4242
*/
43-
private static final byte QUOTE_BYTE = '\"';
43+
protected static final byte QUOTE_BYTE = '\"';
4444

4545
/**
4646
* Extra characters as a byte array
@@ -82,11 +82,6 @@ public abstract class MultipartPart<T extends FileLikePart> implements Closeable
8282
*/
8383
private static final byte[] CONTENT_ID_BYTES = "Content-ID: ".getBytes(US_ASCII);
8484

85-
/**
86-
* Attachment's file name as a byte array
87-
*/
88-
private static final byte[] FILE_NAME_BYTES = "; filename=".getBytes(US_ASCII);
89-
9085
protected final T part;
9186
protected final byte[] boundary;
9287

@@ -269,12 +264,6 @@ protected void visitDispositionHeader(PartVisitor visitor) {
269264
visitor.withBytes(part.getName().getBytes(US_ASCII));
270265
visitor.withByte(QUOTE_BYTE);
271266
}
272-
if (part.getFileName() != null) {
273-
visitor.withBytes(FILE_NAME_BYTES);
274-
visitor.withByte(QUOTE_BYTE);
275-
visitor.withBytes(part.getFileName().getBytes(part.getCharset() != null ? part.getCharset() : US_ASCII));
276-
visitor.withByte(QUOTE_BYTE);
277-
}
278267
}
279268

280269
protected void visitContentTypeHeader(PartVisitor visitor) {
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright (c) 2015 AsyncHttpClient Project. All rights reserved.
3+
*
4+
* This program is licensed to you under the Apache License Version 2.0,
5+
* and you may not use this file except in compliance with the Apache License Version 2.0.
6+
* You may obtain a copy of the Apache License Version 2.0 at
7+
* http://www.apache.org/licenses/LICENSE-2.0.
8+
*
9+
* Unless required by applicable law or agreed to in writing,
10+
* software distributed under the Apache License Version 2.0 is distributed on an
11+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
13+
*/
14+
package org.asynchttpclient.request.body.multipart.part;
15+
16+
import io.netty.buffer.ByteBuf;
17+
import io.netty.buffer.Unpooled;
18+
19+
import java.io.IOException;
20+
import java.nio.channels.WritableByteChannel;
21+
22+
import org.asynchttpclient.request.body.multipart.StringPart;
23+
24+
public class StringMultipartPart extends MultipartPart<StringPart> {
25+
26+
private final ByteBuf contentBuffer;
27+
28+
public StringMultipartPart(StringPart part, byte[] boundary) {
29+
super(part, boundary);
30+
contentBuffer = Unpooled.wrappedBuffer(part.getValue().getBytes(part.getCharset()));
31+
}
32+
33+
@Override
34+
protected long getContentLength() {
35+
return contentBuffer.capacity();
36+
}
37+
38+
@Override
39+
protected long transferContentTo(ByteBuf target) throws IOException {
40+
return transfer(contentBuffer, target, MultipartState.POST_CONTENT);
41+
}
42+
43+
@Override
44+
protected long transferContentTo(WritableByteChannel target) throws IOException {
45+
return transfer(contentBuffer, target, MultipartState.POST_CONTENT);
46+
}
47+
48+
@Override
49+
public void close() {
50+
super.close();
51+
contentBuffer.release();
52+
}
53+
}

0 commit comments

Comments
 (0)