Skip to content

Commit

Permalink
feat: Enforce exact byte reading from Content-Length header for `Bo…
Browse files Browse the repository at this point in the history
…xFile` representation (#1274)
  • Loading branch information
lukaszsocha2 authored Nov 21, 2024
1 parent 50f5a61 commit 0b45cdb
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 4 deletions.
5 changes: 3 additions & 2 deletions src/intTest/java/com/box/sdk/BoxUserIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.net.URLDecoder;
import java.util.Calendar;
import java.util.Collection;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import okhttp3.OkHttpClient;
Expand All @@ -35,8 +36,8 @@
*/
public class BoxUserIT {

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

@BeforeClass
public static void cleanup() {
Expand Down
71 changes: 70 additions & 1 deletion src/main/java/com/box/sdk/BinaryBodyUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,40 @@ static void writeStream(BoxAPIResponse response, OutputStream output, ProgressLi
}

/**
* Writes content of input stream to provided output. Method is NOT closing input stream.
* Writes response body bytes to output stream. After all closes the input stream.
*
* @param response Response that is going to be written.
* @param output Output stream.
*/

static void writeStreamWithContentLength(BoxAPIResponse response, OutputStream output) {
writeStreamWithContentLength(response, output, null);
}

/**
* Writes response body bytes to output stream. After all closes the input stream.
*
* @param response Response that is going to be written.
* @param output Output stream.
* @param listener Listener that will be notified on writing response. Can be null.
*/

static void writeStreamWithContentLength(BoxAPIResponse response, OutputStream output, ProgressListener listener) {
try {
InputStream input;
if (listener != null) {
input = response.getBody(listener);
} else {
input = response.getBody();
}
writeStreamTo(input, output, response.getContentLength());
} finally {
response.close();
}
}

/**
* Writes content of input stream to provided output.
*
* @param input Input that will be read.
* @param output Output stream.
Expand All @@ -71,4 +104,40 @@ static void writeStreamTo(InputStream input, OutputStream output) {
}
}
}

/**
* Writes the content of the input stream to the provided output stream, ensuring the exact number of bytes specified
* by the expected length is written. If the stream ends prematurely an exception is thrown.
*
* @param input The input stream to be read.
* @param output The output stream where data will be written.
* @param expectedLength The expected number of bytes to be transferred.
*/

static void writeStreamTo(InputStream input, OutputStream output, long expectedLength) {
long totalBytesRead = 0;
if (expectedLength < 0) {
throw new RuntimeException("Expected content length should not be negative: " + expectedLength);
}
try {
byte[] buffer = new byte[BUFFER_SIZE];
for (int n = input.read(buffer); n != -1; n = input.read(buffer)) {
output.write(buffer, 0, n);
totalBytesRead += n; // Track the total bytes read
}
if (totalBytesRead != expectedLength) {
throw new IOException("Stream ended prematurely. Expected " + expectedLength
+ " bytes, but read " + totalBytesRead + " bytes.");
}
} catch (IOException e) {
throw new RuntimeException("Error during streaming: " + e.getMessage(), e);
} finally {
try {
input.close();
output.close();
} catch (IOException closeException) {
throw new RuntimeException("IOException during stream close", closeException);
}
}
}
}
3 changes: 2 additions & 1 deletion src/main/java/com/box/sdk/BoxFile.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.box.sdk;

import static com.box.sdk.BinaryBodyUtils.writeStream;
import static com.box.sdk.BinaryBodyUtils.writeStreamWithContentLength;
import static com.box.sdk.http.ContentType.APPLICATION_JSON;
import static com.box.sdk.http.ContentType.APPLICATION_JSON_PATCH;
import static com.eclipsesource.json.Json.NULL;
Expand Down Expand Up @@ -602,7 +603,7 @@ private void makeRepresentationContentRequest(
URL repURL = new URL(representationURLTemplate.replace("{+asset_path}", assetPath));
BoxAPIRequest repContentReq = new BoxAPIRequest(this.getAPI(), repURL, HttpMethod.GET);
BoxAPIResponse response = repContentReq.send();
writeStream(response, output);
writeStreamWithContentLength(response, output);
} catch (MalformedURLException ex) {

throw new BoxAPIException("Could not generate representation content URL");
Expand Down
2 changes: 2 additions & 0 deletions src/test/java/com/box/sdk/BoxFileTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,7 @@ public void testGetThumbnailSucceeds() {
))
.willReturn(WireMock.aResponse()
.withHeader("Content-Type", "image/jpg")
.withHeader("Content-Length", String.valueOf(13))
.withBody("This is a JPG")
.withStatus(200))
);
Expand Down Expand Up @@ -517,6 +518,7 @@ public void testGetRepresentationContentSuccess() {
))
.willReturn(WireMock.aResponse()
.withHeader("Content-Type", "image/jpg")
.withHeader("Content-Length", String.valueOf(13))
.withBody("This is a JPG")
.withStatus(200))
);
Expand Down

0 comments on commit 0b45cdb

Please sign in to comment.