Skip to content

Commit 91c20a3

Browse files
dmitry-fachingor13
authored andcommitted
fix: use random UUID for multipart boundary delimiter (#916)
* fix: use random uuid string as boundary * fix: use random uuid string as boundary
1 parent d1fe119 commit 91c20a3

File tree

2 files changed

+74
-47
lines changed

2 files changed

+74
-47
lines changed

google-http-client/src/main/java/com/google/api/client/http/MultipartContent.java

+10-5
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,15 @@
2424
import java.util.Arrays;
2525
import java.util.Collection;
2626
import java.util.Collections;
27+
import java.util.UUID;
2728

2829
/**
2930
* Serializes MIME multipart content as specified by <a
3031
* href="http://tools.ietf.org/html/rfc2387">RFC 2387: The MIME Multipart/Related Content-type</a>
3132
* and <a href="http://tools.ietf.org/html/rfc1521#section-7.2.2">RFC 2046: Multipurpose Internet
3233
* Mail Extensions: The Multipart/mixed (primary) subtype</a>.
3334
*
34-
* <p>By default the media type is {@code "multipart/related; boundary=__END_OF_PART__"}, but this
35+
* <p>By default the media type is {@code "multipart/related; boundary=__END_OF_PART__<random UUID>__"}, but this
3536
* may be customized by calling {@link #setMediaType(HttpMediaType)}, {@link #getMediaType()}, or
3637
* {@link #setBoundary(String)}.
3738
*
@@ -47,10 +48,14 @@ public class MultipartContent extends AbstractHttpContent {
4748
private static final String TWO_DASHES = "--";
4849

4950
/** Parts of the HTTP multipart request. */
50-
private ArrayList<Part> parts = new ArrayList<Part>();
51+
private ArrayList<Part> parts = new ArrayList<>();
5152

5253
public MultipartContent() {
53-
super(new HttpMediaType("multipart/related").setParameter("boundary", "__END_OF_PART__"));
54+
this("__END_OF_PART__" + UUID.randomUUID().toString() + "__");
55+
}
56+
57+
public MultipartContent(String boundary) {
58+
super(new HttpMediaType("multipart/related").setParameter("boundary", boundary));
5459
}
5560

5661
public void writeTo(OutputStream out) throws IOException {
@@ -152,7 +157,7 @@ public MultipartContent addPart(Part part) {
152157
* changing the return type, but nothing else.
153158
*/
154159
public MultipartContent setParts(Collection<Part> parts) {
155-
this.parts = new ArrayList<Part>(parts);
160+
this.parts = new ArrayList<>(parts);
156161
return this;
157162
}
158163

@@ -164,7 +169,7 @@ public MultipartContent setParts(Collection<Part> parts) {
164169
* changing the return type, but nothing else.
165170
*/
166171
public MultipartContent setContentParts(Collection<? extends HttpContent> contentParts) {
167-
this.parts = new ArrayList<Part>(contentParts.size());
172+
this.parts = new ArrayList<>(contentParts.size());
168173
for (HttpContent contentPart : contentParts) {
169174
addPart(new Part(contentPart));
170175
}

google-http-client/src/test/java/com/google/api/client/http/MultipartContentTest.java

+64-42
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package com.google.api.client.http;
1616

1717
import com.google.api.client.json.Json;
18+
import com.google.api.client.util.Charsets;
1819
import com.google.api.client.util.StringUtils;
1920
import java.io.ByteArrayOutputStream;
2021
import junit.framework.TestCase;
@@ -26,55 +27,76 @@
2627
*/
2728
public class MultipartContentTest extends TestCase {
2829

30+
private static final String BOUNDARY = "__END_OF_PART__";
2931
private static final String CRLF = "\r\n";
3032
private static final String CONTENT_TYPE = Json.MEDIA_TYPE;
31-
private static final String HEADERS =
32-
"Content-Length: 3"
33-
+ CRLF
34-
+ "Content-Type: application/json; charset=UTF-8"
35-
+ CRLF
36-
+ "content-transfer-encoding: binary"
37-
+ CRLF;
33+
private static final String HEADERS = headers("application/json; charset=UTF-8", "foo");
34+
35+
private static String headers(String contentType, String value) {
36+
return "Content-Length: " + value.length() + CRLF
37+
+ "Content-Type: " + contentType + CRLF
38+
+ "content-transfer-encoding: binary" + CRLF;
39+
}
40+
41+
public void testRandomContent() throws Exception {
42+
MultipartContent content = new MultipartContent();
43+
String boundaryString = content.getBoundary();
44+
assertNotNull(boundaryString);
45+
assertTrue(boundaryString.startsWith(BOUNDARY));
46+
assertTrue(boundaryString.endsWith("__"));
47+
assertEquals("multipart/related; boundary=" + boundaryString, content.getType());
48+
49+
final String[][] VALUES = new String[][] {
50+
{"Hello world", "text/plain"},
51+
{"<xml>Hi</xml>", "application/xml"},
52+
{"{x:1,y:2}", "application/json"}
53+
};
54+
StringBuilder expectedStringBuilder = new StringBuilder();
55+
for (String[] valueTypePair: VALUES) {
56+
String contentValue = valueTypePair[0];
57+
String contentType = valueTypePair[1];
58+
content.addPart(new MultipartContent.Part(ByteArrayContent.fromString(contentType, contentValue)));
59+
expectedStringBuilder.append("--").append(boundaryString).append(CRLF)
60+
.append(headers(contentType, contentValue)).append(CRLF)
61+
.append(contentValue).append(CRLF);
62+
}
63+
expectedStringBuilder.append("--").append(boundaryString).append("--").append(CRLF);
64+
// write to string
65+
ByteArrayOutputStream out = new ByteArrayOutputStream();
66+
content.writeTo(out);
67+
String expectedContent = expectedStringBuilder.toString();
68+
assertEquals(expectedContent, out.toString(Charsets.UTF_8.name()));
69+
assertEquals(StringUtils.getBytesUtf8(expectedContent).length, content.getLength());
70+
}
3871

3972
public void testContent() throws Exception {
40-
subtestContent("--__END_OF_PART__--" + CRLF, null);
73+
subtestContent("--" + BOUNDARY + "--" + CRLF, null);
4174
subtestContent(
42-
"--__END_OF_PART__" + CRLF + HEADERS + CRLF + "foo" + CRLF + "--__END_OF_PART__--" + CRLF,
43-
null,
75+
"--" + BOUNDARY + CRLF
76+
+ HEADERS + CRLF
77+
+ "foo" + CRLF
78+
+ "--" + BOUNDARY + "--" + CRLF,
79+
null,
4480
"foo");
4581
subtestContent(
46-
"--__END_OF_PART__"
47-
+ CRLF
48-
+ HEADERS
49-
+ CRLF
50-
+ "foo"
51-
+ CRLF
52-
+ "--__END_OF_PART__"
53-
+ CRLF
54-
+ HEADERS
55-
+ CRLF
56-
+ "bar"
57-
+ CRLF
58-
+ "--__END_OF_PART__--"
59-
+ CRLF,
60-
null,
82+
"--" + BOUNDARY + CRLF
83+
+ HEADERS + CRLF
84+
+ "foo" + CRLF
85+
+ "--" + BOUNDARY + CRLF
86+
+ HEADERS + CRLF
87+
+ "bar" + CRLF
88+
+ "--" + BOUNDARY + "--" + CRLF,
89+
null,
6190
"foo",
6291
"bar");
6392
subtestContent(
64-
"--myboundary"
65-
+ CRLF
66-
+ HEADERS
67-
+ CRLF
68-
+ "foo"
69-
+ CRLF
70-
+ "--myboundary"
71-
+ CRLF
72-
+ HEADERS
73-
+ CRLF
74-
+ "bar"
75-
+ CRLF
76-
+ "--myboundary--"
77-
+ CRLF,
93+
"--myboundary" + CRLF
94+
+ HEADERS + CRLF
95+
+ "foo" + CRLF
96+
+ "--myboundary" + CRLF
97+
+ HEADERS + CRLF
98+
+ "bar" + CRLF
99+
+ "--myboundary--" + CRLF,
78100
"myboundary",
79101
"foo",
80102
"bar");
@@ -83,7 +105,7 @@ public void testContent() throws Exception {
83105
private void subtestContent(String expectedContent, String boundaryString, String... contents)
84106
throws Exception {
85107
// multipart content
86-
MultipartContent content = new MultipartContent();
108+
MultipartContent content = new MultipartContent(boundaryString == null ? BOUNDARY : boundaryString);
87109
for (String contentValue : contents) {
88110
content.addPart(
89111
new MultipartContent.Part(ByteArrayContent.fromString(CONTENT_TYPE, contentValue)));
@@ -94,11 +116,11 @@ private void subtestContent(String expectedContent, String boundaryString, Strin
94116
// write to string
95117
ByteArrayOutputStream out = new ByteArrayOutputStream();
96118
content.writeTo(out);
97-
assertEquals(expectedContent, out.toString());
119+
assertEquals(expectedContent, out.toString(Charsets.UTF_8.name()));
98120
assertEquals(StringUtils.getBytesUtf8(expectedContent).length, content.getLength());
99121
assertEquals(
100122
boundaryString == null
101-
? "multipart/related; boundary=__END_OF_PART__"
123+
? "multipart/related; boundary=" + BOUNDARY
102124
: "multipart/related; boundary=" + boundaryString,
103125
content.getType());
104126
}

0 commit comments

Comments
 (0)