diff --git a/okhttp-tests/src/test/java/com/squareup/okhttp/MultipartBuilderTest.java b/okhttp-tests/src/test/java/com/squareup/okhttp/MultipartBuilderTest.java index 0e12470af..4159679c7 100644 --- a/okhttp-tests/src/test/java/com/squareup/okhttp/MultipartBuilderTest.java +++ b/okhttp-tests/src/test/java/com/squareup/okhttp/MultipartBuilderTest.java @@ -46,7 +46,8 @@ public final class MultipartBuilderTest { Buffer buffer = new Buffer(); requestBody.writeTo(buffer); - assertEquals(-1L, requestBody.contentLength()); + assertEquals(53, requestBody.contentLength()); + assertEquals(buffer.size(), requestBody.contentLength()); assertEquals(expected, buffer.readUtf8()); } @@ -76,7 +77,8 @@ public final class MultipartBuilderTest { Buffer buffer = new Buffer(); requestBody.writeTo(buffer); - assertEquals(-1L, requestBody.contentLength()); + assertEquals(112, requestBody.contentLength()); + assertEquals(buffer.size(), requestBody.contentLength()); assertEquals(expected, buffer.readUtf8()); } @@ -90,6 +92,7 @@ public final class MultipartBuilderTest { + "--AaB03x\r\n" + "Content-Disposition: form-data; name=\"files\"\r\n" + "Content-Type: multipart/mixed; boundary=BbC04y\r\n" + + "Content-Length: 337\r\n" + "\r\n" + "--BbC04y\r\n" + "Content-Disposition: file; filename=\"file1.txt\"\r\n" @@ -131,7 +134,8 @@ public final class MultipartBuilderTest { Buffer buffer = new Buffer(); requestBody.writeTo(buffer); - assertEquals(-1L, requestBody.contentLength()); + assertEquals(568, requestBody.contentLength()); + assertEquals(buffer.size(), requestBody.contentLength()); assertEquals(expected, buffer.readUtf8()); } diff --git a/okhttp/src/main/java/com/squareup/okhttp/MultipartBuilder.java b/okhttp/src/main/java/com/squareup/okhttp/MultipartBuilder.java index 54a3fde70..5b160b674 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/MultipartBuilder.java +++ b/okhttp/src/main/java/com/squareup/okhttp/MultipartBuilder.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.UUID; +import okio.Buffer; import okio.BufferedSink; import okio.ByteString; @@ -197,6 +198,7 @@ public final class MultipartBuilder { private final MediaType contentType; private final List partHeaders; private final List partBodies; + private long contentLength = -1L; public MultipartRequestBody(MediaType type, ByteString boundary, List partHeaders, List partBodies) { @@ -213,10 +215,25 @@ public final class MultipartBuilder { } @Override public long contentLength() throws IOException { - return -1L; + long result = contentLength; + if (result != -1L) return result; + return contentLength = writeOrCountBytes(null, true); } - @Override public void writeTo(BufferedSink sink) throws IOException { + /** + * Either writes this request to {@code sink} or measures its content length. We have one method + * do double-duty to make sure the counting and content are consistent, particularly when it + * comes to awkward operations like measuring the encoded length of header strings, or the + * length-in-digits of an encoded integer. + */ + private long writeOrCountBytes(BufferedSink sink, boolean countBytes) throws IOException { + long byteCount = 0L; + + Buffer byteCountBuffer = null; + if (countBytes) { + sink = byteCountBuffer = new Buffer(); + } + for (int p = 0, partCount = partHeaders.size(); p < partCount; p++) { Headers headers = partHeaders.get(p); RequestBody body = partBodies.get(p); @@ -246,10 +263,20 @@ public final class MultipartBuilder { sink.writeUtf8("Content-Length: ") .writeDecimalLong(contentLength) .write(CRLF); + } else if (countBytes) { + // We can't measure the body's size without the sizes of its components. + byteCountBuffer.clear(); + return -1L; } sink.write(CRLF); - partBodies.get(p).writeTo(sink); + + if (countBytes) { + byteCount += contentLength; + } else { + partBodies.get(p).writeTo(sink); + } + sink.write(CRLF); } @@ -257,6 +284,17 @@ public final class MultipartBuilder { sink.write(boundary); sink.write(DASHDASH); sink.write(CRLF); + + if (countBytes) { + byteCount += byteCountBuffer.size(); + byteCountBuffer.clear(); + } + + return byteCount; + } + + @Override public void writeTo(BufferedSink sink) throws IOException { + writeOrCountBytes(sink, false); } } }