Skip to content

Commit 0b45cdb

Browse files
authored
feat: Enforce exact byte reading from Content-Length header for BoxFile representation (#1274)
1 parent 50f5a61 commit 0b45cdb

File tree

4 files changed

+77
-4
lines changed

4 files changed

+77
-4
lines changed

src/intTest/java/com/box/sdk/BoxUserIT.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.net.URLDecoder;
2424
import java.util.Calendar;
2525
import java.util.Collection;
26+
import java.util.UUID;
2627
import java.util.logging.Level;
2728
import java.util.logging.Logger;
2829
import okhttp3.OkHttpClient;
@@ -35,8 +36,8 @@
3536
*/
3637
public class BoxUserIT {
3738

38-
private static final String NEW_USER_LOGIN = "login2@boz.com";
39-
private static final String NEW_USER_NAME = "non-empty name";
39+
private static final String NEW_USER_LOGIN = UUID.randomUUID() + "@boz.com";
40+
private static final String NEW_USER_NAME = UUID.randomUUID().toString();
4041

4142
@BeforeClass
4243
public static void cleanup() {

src/main/java/com/box/sdk/BinaryBodyUtils.java

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,40 @@ static void writeStream(BoxAPIResponse response, OutputStream output, ProgressLi
4747
}
4848

4949
/**
50-
* Writes content of input stream to provided output. Method is NOT closing input stream.
50+
* Writes response body bytes to output stream. After all closes the input stream.
51+
*
52+
* @param response Response that is going to be written.
53+
* @param output Output stream.
54+
*/
55+
56+
static void writeStreamWithContentLength(BoxAPIResponse response, OutputStream output) {
57+
writeStreamWithContentLength(response, output, null);
58+
}
59+
60+
/**
61+
* Writes response body bytes to output stream. After all closes the input stream.
62+
*
63+
* @param response Response that is going to be written.
64+
* @param output Output stream.
65+
* @param listener Listener that will be notified on writing response. Can be null.
66+
*/
67+
68+
static void writeStreamWithContentLength(BoxAPIResponse response, OutputStream output, ProgressListener listener) {
69+
try {
70+
InputStream input;
71+
if (listener != null) {
72+
input = response.getBody(listener);
73+
} else {
74+
input = response.getBody();
75+
}
76+
writeStreamTo(input, output, response.getContentLength());
77+
} finally {
78+
response.close();
79+
}
80+
}
81+
82+
/**
83+
* Writes content of input stream to provided output.
5184
*
5285
* @param input Input that will be read.
5386
* @param output Output stream.
@@ -71,4 +104,40 @@ static void writeStreamTo(InputStream input, OutputStream output) {
71104
}
72105
}
73106
}
107+
108+
/**
109+
* Writes the content of the input stream to the provided output stream, ensuring the exact number of bytes specified
110+
* by the expected length is written. If the stream ends prematurely an exception is thrown.
111+
*
112+
* @param input The input stream to be read.
113+
* @param output The output stream where data will be written.
114+
* @param expectedLength The expected number of bytes to be transferred.
115+
*/
116+
117+
static void writeStreamTo(InputStream input, OutputStream output, long expectedLength) {
118+
long totalBytesRead = 0;
119+
if (expectedLength < 0) {
120+
throw new RuntimeException("Expected content length should not be negative: " + expectedLength);
121+
}
122+
try {
123+
byte[] buffer = new byte[BUFFER_SIZE];
124+
for (int n = input.read(buffer); n != -1; n = input.read(buffer)) {
125+
output.write(buffer, 0, n);
126+
totalBytesRead += n; // Track the total bytes read
127+
}
128+
if (totalBytesRead != expectedLength) {
129+
throw new IOException("Stream ended prematurely. Expected " + expectedLength
130+
+ " bytes, but read " + totalBytesRead + " bytes.");
131+
}
132+
} catch (IOException e) {
133+
throw new RuntimeException("Error during streaming: " + e.getMessage(), e);
134+
} finally {
135+
try {
136+
input.close();
137+
output.close();
138+
} catch (IOException closeException) {
139+
throw new RuntimeException("IOException during stream close", closeException);
140+
}
141+
}
142+
}
74143
}

src/main/java/com/box/sdk/BoxFile.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.box.sdk;
22

33
import static com.box.sdk.BinaryBodyUtils.writeStream;
4+
import static com.box.sdk.BinaryBodyUtils.writeStreamWithContentLength;
45
import static com.box.sdk.http.ContentType.APPLICATION_JSON;
56
import static com.box.sdk.http.ContentType.APPLICATION_JSON_PATCH;
67
import static com.eclipsesource.json.Json.NULL;
@@ -602,7 +603,7 @@ private void makeRepresentationContentRequest(
602603
URL repURL = new URL(representationURLTemplate.replace("{+asset_path}", assetPath));
603604
BoxAPIRequest repContentReq = new BoxAPIRequest(this.getAPI(), repURL, HttpMethod.GET);
604605
BoxAPIResponse response = repContentReq.send();
605-
writeStream(response, output);
606+
writeStreamWithContentLength(response, output);
606607
} catch (MalformedURLException ex) {
607608

608609
throw new BoxAPIException("Could not generate representation content URL");

src/test/java/com/box/sdk/BoxFileTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,7 @@ public void testGetThumbnailSucceeds() {
433433
))
434434
.willReturn(WireMock.aResponse()
435435
.withHeader("Content-Type", "image/jpg")
436+
.withHeader("Content-Length", String.valueOf(13))
436437
.withBody("This is a JPG")
437438
.withStatus(200))
438439
);
@@ -517,6 +518,7 @@ public void testGetRepresentationContentSuccess() {
517518
))
518519
.willReturn(WireMock.aResponse()
519520
.withHeader("Content-Type", "image/jpg")
521+
.withHeader("Content-Length", String.valueOf(13))
520522
.withBody("This is a JPG")
521523
.withStatus(200))
522524
);

0 commit comments

Comments
 (0)