From 91c20a3dfb654e85104b1c09a0b2befbae356c19 Mon Sep 17 00:00:00 2001 From: Dmitry <58846611+dmitry-fa@users.noreply.github.com> Date: Tue, 31 Dec 2019 01:26:10 +0300 Subject: [PATCH] fix: use random UUID for multipart boundary delimiter (#916) * fix: use random uuid string as boundary * fix: use random uuid string as boundary --- .../api/client/http/MultipartContent.java | 15 ++- .../api/client/http/MultipartContentTest.java | 106 +++++++++++------- 2 files changed, 74 insertions(+), 47 deletions(-) diff --git a/google-http-client/src/main/java/com/google/api/client/http/MultipartContent.java b/google-http-client/src/main/java/com/google/api/client/http/MultipartContent.java index 4fdc0b586..43a58b446 100644 --- a/google-http-client/src/main/java/com/google/api/client/http/MultipartContent.java +++ b/google-http-client/src/main/java/com/google/api/client/http/MultipartContent.java @@ -24,6 +24,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.UUID; /** * Serializes MIME multipart content as specified by RFC 2046: Multipurpose Internet * Mail Extensions: The Multipart/mixed (primary) subtype. * - *

By default the media type is {@code "multipart/related; boundary=__END_OF_PART__"}, but this + *

By default the media type is {@code "multipart/related; boundary=__END_OF_PART____"}, but this * may be customized by calling {@link #setMediaType(HttpMediaType)}, {@link #getMediaType()}, or * {@link #setBoundary(String)}. * @@ -47,10 +48,14 @@ public class MultipartContent extends AbstractHttpContent { private static final String TWO_DASHES = "--"; /** Parts of the HTTP multipart request. */ - private ArrayList parts = new ArrayList(); + private ArrayList parts = new ArrayList<>(); public MultipartContent() { - super(new HttpMediaType("multipart/related").setParameter("boundary", "__END_OF_PART__")); + this("__END_OF_PART__" + UUID.randomUUID().toString() + "__"); + } + + public MultipartContent(String boundary) { + super(new HttpMediaType("multipart/related").setParameter("boundary", boundary)); } public void writeTo(OutputStream out) throws IOException { @@ -152,7 +157,7 @@ public MultipartContent addPart(Part part) { * changing the return type, but nothing else. */ public MultipartContent setParts(Collection parts) { - this.parts = new ArrayList(parts); + this.parts = new ArrayList<>(parts); return this; } @@ -164,7 +169,7 @@ public MultipartContent setParts(Collection parts) { * changing the return type, but nothing else. */ public MultipartContent setContentParts(Collection contentParts) { - this.parts = new ArrayList(contentParts.size()); + this.parts = new ArrayList<>(contentParts.size()); for (HttpContent contentPart : contentParts) { addPart(new Part(contentPart)); } diff --git a/google-http-client/src/test/java/com/google/api/client/http/MultipartContentTest.java b/google-http-client/src/test/java/com/google/api/client/http/MultipartContentTest.java index 76f725b61..14e0e5990 100644 --- a/google-http-client/src/test/java/com/google/api/client/http/MultipartContentTest.java +++ b/google-http-client/src/test/java/com/google/api/client/http/MultipartContentTest.java @@ -15,6 +15,7 @@ package com.google.api.client.http; import com.google.api.client.json.Json; +import com.google.api.client.util.Charsets; import com.google.api.client.util.StringUtils; import java.io.ByteArrayOutputStream; import junit.framework.TestCase; @@ -26,55 +27,76 @@ */ public class MultipartContentTest extends TestCase { + private static final String BOUNDARY = "__END_OF_PART__"; private static final String CRLF = "\r\n"; private static final String CONTENT_TYPE = Json.MEDIA_TYPE; - private static final String HEADERS = - "Content-Length: 3" - + CRLF - + "Content-Type: application/json; charset=UTF-8" - + CRLF - + "content-transfer-encoding: binary" - + CRLF; + private static final String HEADERS = headers("application/json; charset=UTF-8", "foo"); + + private static String headers(String contentType, String value) { + return "Content-Length: " + value.length() + CRLF + + "Content-Type: " + contentType + CRLF + + "content-transfer-encoding: binary" + CRLF; + } + + public void testRandomContent() throws Exception { + MultipartContent content = new MultipartContent(); + String boundaryString = content.getBoundary(); + assertNotNull(boundaryString); + assertTrue(boundaryString.startsWith(BOUNDARY)); + assertTrue(boundaryString.endsWith("__")); + assertEquals("multipart/related; boundary=" + boundaryString, content.getType()); + + final String[][] VALUES = new String[][] { + {"Hello world", "text/plain"}, + {"Hi", "application/xml"}, + {"{x:1,y:2}", "application/json"} + }; + StringBuilder expectedStringBuilder = new StringBuilder(); + for (String[] valueTypePair: VALUES) { + String contentValue = valueTypePair[0]; + String contentType = valueTypePair[1]; + content.addPart(new MultipartContent.Part(ByteArrayContent.fromString(contentType, contentValue))); + expectedStringBuilder.append("--").append(boundaryString).append(CRLF) + .append(headers(contentType, contentValue)).append(CRLF) + .append(contentValue).append(CRLF); + } + expectedStringBuilder.append("--").append(boundaryString).append("--").append(CRLF); + // write to string + ByteArrayOutputStream out = new ByteArrayOutputStream(); + content.writeTo(out); + String expectedContent = expectedStringBuilder.toString(); + assertEquals(expectedContent, out.toString(Charsets.UTF_8.name())); + assertEquals(StringUtils.getBytesUtf8(expectedContent).length, content.getLength()); + } public void testContent() throws Exception { - subtestContent("--__END_OF_PART__--" + CRLF, null); + subtestContent("--" + BOUNDARY + "--" + CRLF, null); subtestContent( - "--__END_OF_PART__" + CRLF + HEADERS + CRLF + "foo" + CRLF + "--__END_OF_PART__--" + CRLF, - null, + "--" + BOUNDARY + CRLF + + HEADERS + CRLF + + "foo" + CRLF + + "--" + BOUNDARY + "--" + CRLF, + null, "foo"); subtestContent( - "--__END_OF_PART__" - + CRLF - + HEADERS - + CRLF - + "foo" - + CRLF - + "--__END_OF_PART__" - + CRLF - + HEADERS - + CRLF - + "bar" - + CRLF - + "--__END_OF_PART__--" - + CRLF, - null, + "--" + BOUNDARY + CRLF + + HEADERS + CRLF + + "foo" + CRLF + + "--" + BOUNDARY + CRLF + + HEADERS + CRLF + + "bar" + CRLF + + "--" + BOUNDARY + "--" + CRLF, + null, "foo", "bar"); subtestContent( - "--myboundary" - + CRLF - + HEADERS - + CRLF - + "foo" - + CRLF - + "--myboundary" - + CRLF - + HEADERS - + CRLF - + "bar" - + CRLF - + "--myboundary--" - + CRLF, + "--myboundary" + CRLF + + HEADERS + CRLF + + "foo" + CRLF + + "--myboundary" + CRLF + + HEADERS + CRLF + + "bar" + CRLF + + "--myboundary--" + CRLF, "myboundary", "foo", "bar"); @@ -83,7 +105,7 @@ public void testContent() throws Exception { private void subtestContent(String expectedContent, String boundaryString, String... contents) throws Exception { // multipart content - MultipartContent content = new MultipartContent(); + MultipartContent content = new MultipartContent(boundaryString == null ? BOUNDARY : boundaryString); for (String contentValue : contents) { content.addPart( new MultipartContent.Part(ByteArrayContent.fromString(CONTENT_TYPE, contentValue))); @@ -94,11 +116,11 @@ private void subtestContent(String expectedContent, String boundaryString, Strin // write to string ByteArrayOutputStream out = new ByteArrayOutputStream(); content.writeTo(out); - assertEquals(expectedContent, out.toString()); + assertEquals(expectedContent, out.toString(Charsets.UTF_8.name())); assertEquals(StringUtils.getBytesUtf8(expectedContent).length, content.getLength()); assertEquals( boundaryString == null - ? "multipart/related; boundary=__END_OF_PART__" + ? "multipart/related; boundary=" + BOUNDARY : "multipart/related; boundary=" + boundaryString, content.getType()); }