• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

box / box-java-sdk / #4225

29 Nov 2024 08:36AM UTC coverage: 71.714% (-0.06%) from 71.773%
#4225

push

github

web-flow
fix: Correctly calculate `Content-Length` when reading from a stream (#1277)

4 of 15 new or added lines in 1 file covered. (26.67%)

1 existing line in 1 file now uncovered.

8070 of 11253 relevant lines covered (71.71%)

0.72 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

62.71
/src/main/java/com/box/sdk/BinaryBodyUtils.java
1
package com.box.sdk;
2

3
import com.box.sdk.http.HttpHeaders;
4
import java.io.IOException;
5
import java.io.InputStream;
6
import java.io.OutputStream;
7

8
/**
9
 * Utility class to help writing binary data to output stream.
10
 */
11
final class BinaryBodyUtils {
12
    private static final int BUFFER_SIZE = 8192;
13

14
    private BinaryBodyUtils() {
15
        // utility class has no public constructor
16
    }
17

18
    /**
19
     * Writes response body bytes to output stream. After all closes the input stream.
20
     *
21
     * @param response Response that is going to be written.
22
     * @param output   Output stream.
23
     */
24
    static void writeStream(BoxAPIResponse response, OutputStream output) {
25
        writeStream(response, output, null);
1✔
26
    }
1✔
27

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

36
    static void writeStream(BoxAPIResponse response, OutputStream output, ProgressListener listener) {
37
        try {
38
            InputStream input;
39
            if (listener != null) {
1✔
40
                input = response.getBody(listener);
×
41
            } else {
42
                input = response.getBody();
1✔
43
            }
44
            writeStreamTo(input, output);
1✔
45
        } finally {
46
            response.close();
1✔
47
        }
48
    }
1✔
49

50
    /**
51
     * Writes response body bytes to output stream. After all closes the input stream.
52
     *
53
     * @param response Response that is going to be written.
54
     * @param output   Output stream.
55
     */
56

57
    static void writeStreamWithContentLength(BoxAPIResponse response, OutputStream output) {
58
        writeStreamWithContentLength(response, output, null);
1✔
59
    }
1✔
60

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

69
    static void writeStreamWithContentLength(BoxAPIResponse response, OutputStream output, ProgressListener listener) {
70
        try {
71
            InputStream input;
72
            if (listener != null) {
1✔
73
                input = response.getBody(listener);
×
74
            } else {
75
                input = response.getBody();
1✔
76
            }
77
            writeStreamTo(input, output, getContentLengthFromAPIResponse(response));
1✔
78
        } finally {
79
            response.close();
1✔
80
        }
81
    }
1✔
82

83
    /**
84
     * Get the content length from the API response.
85
     * In some cases, the Content-Length is not provided in the response headers.
86
     * This could happen when getting the content representation for a compressed data.
87
     * In that case the API will switch to chunk mode and provide the length in the "X-Original-Content-Length" header.
88
     *
89
     * @param response API response.
90
     * @return Content length.
91
     */
92
    private static long getContentLengthFromAPIResponse(BoxAPIResponse response) {
93
        long length = response.getContentLength();
1✔
94
        if (length == -1) {
1✔
NEW
95
            String headerValue = null;
×
NEW
96
            if (response.getHeaders().containsKey(HttpHeaders.CONTENT_LENGTH)) {
×
NEW
97
                headerValue = response.getHeaders().get(HttpHeaders.CONTENT_LENGTH).get(0);
×
NEW
98
            } else if (response.getHeaders().containsKey(HttpHeaders.X_ORIGINAL_CONTENT_LENGTH)) {
×
NEW
99
                headerValue = response.getHeaders().get(HttpHeaders.X_ORIGINAL_CONTENT_LENGTH).get(0);
×
100
            }
101

NEW
102
            if (headerValue != null) {
×
103
                try {
NEW
104
                    length = Integer.parseInt(headerValue);
×
NEW
105
                } catch (NumberFormatException e) {
×
NEW
106
                    throw new RuntimeException("Invalid content length: " + headerValue);
×
NEW
107
                }
×
108
            }
109
        }
110

111
        return length;
1✔
112
    }
113

114
    /**
115
     * Writes content of input stream to provided output.
116
     *
117
     * @param input  Input that will be read.
118
     * @param output Output stream.
119
     */
120
    static void writeStreamTo(InputStream input, OutputStream output) {
121
        try {
122
            byte[] buffer = new byte[BUFFER_SIZE];
1✔
123
            int n = input.read(buffer);
1✔
124
            while (n != -1) {
1✔
125
                output.write(buffer, 0, n);
1✔
126
                n = input.read(buffer);
1✔
127
            }
128
        } catch (IOException e) {
×
129
            throw new RuntimeException(e);
×
130
        } finally {
131
            try {
132
                input.close();
1✔
133
                output.close();
1✔
134
            } catch (IOException e) {
×
135
                throw new RuntimeException(e);
×
136
            }
1✔
137
        }
138
    }
1✔
139

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

149
    static void writeStreamTo(InputStream input, OutputStream output, long expectedLength) {
150
        long totalBytesRead = 0;
1✔
151
        if (expectedLength < 0) {
1✔
152
            throw new RuntimeException("Expected content length should not be negative: " + expectedLength);
×
153
        }
154
        try {
155
            byte[] buffer = new byte[BUFFER_SIZE];
1✔
156
            for (int n = input.read(buffer); n != -1; n = input.read(buffer)) {
1✔
157
                output.write(buffer, 0, n);
1✔
158
                totalBytesRead += n;  // Track the total bytes read
1✔
159
            }
160
            if (totalBytesRead != expectedLength) {
1✔
NEW
161
                throw new IOException("Stream ended prematurely. Expected "
×
162
                    + expectedLength + " bytes, but read " + totalBytesRead + " bytes.");
163
            }
164
        } catch (IOException e) {
×
165
            throw new RuntimeException("Error during streaming: " + e.getMessage(), e);
×
166
        } finally {
167
            try {
168
                input.close();
1✔
169
                output.close();
1✔
170
            } catch (IOException closeException) {
×
171
                throw new RuntimeException("IOException during stream close", closeException);
×
172
            }
1✔
173
        }
174
    }
1✔
175
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc