16
16
package software .amazon .awssdk .http .auth .aws .internal .signer .chunkedencoding ;
17
17
18
18
import java .io .ByteArrayInputStream ;
19
- import java .io .ByteArrayOutputStream ;
20
19
import java .io .IOException ;
21
20
import java .io .InputStream ;
21
+ import java .io .SequenceInputStream ;
22
+ import java .nio .ByteBuffer ;
22
23
import java .nio .charset .StandardCharsets ;
23
24
import java .util .ArrayList ;
24
- import java .util .Arrays ;
25
+ import java .util .Collections ;
25
26
import java .util .List ;
26
27
import software .amazon .awssdk .annotations .SdkInternalApi ;
27
28
import software .amazon .awssdk .utils .Logger ;
@@ -52,6 +53,10 @@ public final class ChunkedEncodedInputStream extends InputStream {
52
53
private static final Logger LOG = Logger .loggerFor (ChunkedEncodedInputStream .class );
53
54
private static final byte [] CRLF = {'\r' , '\n' };
54
55
private static final byte [] END = {};
56
+ private static final byte [] SEMICOLON = {';' };
57
+ private static final byte [] EQUALS = {'=' };
58
+ private static final byte [] COLON = {':' };
59
+ private static final byte [] COMMA = {',' };
55
60
56
61
private final InputStream inputStream ;
57
62
private final int chunkSize ;
@@ -101,14 +106,14 @@ private Chunk getChunk(InputStream stream) throws IOException {
101
106
if (currentChunk != null ) {
102
107
currentChunk .close ();
103
108
}
104
- // we *have* to read from the backing stream in order to figure out if it's the end or not
105
- // TODO(sra-identity-and-auth): We can likely optimize this by not copying the entire chunk of data into memory
109
+
110
+ // We have to read from the input stream into a format that can be used for signing and headers.
106
111
byte [] chunkData = new byte [chunkSize ];
107
112
int read = read (stream , chunkData , chunkSize );
108
113
109
114
if (read > 0 ) {
110
115
// set the current chunk to the newly written chunk
111
- return getNextChunk (Arrays . copyOf (chunkData , read ));
116
+ return getNextChunk (ByteBuffer . wrap (chunkData , 0 , read ));
112
117
}
113
118
114
119
LOG .debug (() -> "End of backing stream reached. Reading final chunk." );
@@ -142,58 +147,71 @@ private int read(InputStream inputStream, byte[] buf, int maxBytesToRead) throws
142
147
* Create a chunk from a byte-array, which includes the header, the extensions, and the chunk data. The input array should be
143
148
* correctly sized, i.e. the number of bytes should equal its length.
144
149
*/
145
- private Chunk getNextChunk (byte [] data ) throws IOException {
146
- ByteArrayOutputStream chunkStream = new ByteArrayOutputStream ();
147
- writeChunk ( data , chunkStream );
148
- chunkStream . write ( CRLF );
149
- byte [] newChunkData = chunkStream . toByteArray ();
150
-
151
- return Chunk .create (new ByteArrayInputStream ( newChunkData ) , newChunkData .length );
150
+ private Chunk getNextChunk (ByteBuffer data ) {
151
+ LengthAwareSequenceInputStream newChunkData =
152
+ LengthAwareSequenceInputStream . builder ()
153
+ . add ( createChunkStream ( data ))
154
+ . add ( CRLF )
155
+ . build ();
156
+ return Chunk .create (newChunkData , newChunkData .size );
152
157
}
153
158
154
159
/**
155
160
* Create the final chunk, which includes the header, the extensions, the chunk (if applicable), and the trailer
156
161
*/
157
162
private Chunk getFinalChunk () throws IOException {
158
- ByteArrayOutputStream chunkStream = new ByteArrayOutputStream ();
159
- writeChunk (END , chunkStream );
160
- writeTrailers (chunkStream );
161
- chunkStream .write (CRLF );
162
- byte [] newChunkData = chunkStream .toByteArray ();
163
-
164
- return Chunk .create (new ByteArrayInputStream (newChunkData ), newChunkData .length );
163
+ LengthAwareSequenceInputStream chunkData =
164
+ LengthAwareSequenceInputStream .builder ()
165
+ .add (createChunkStream (ByteBuffer .wrap (END )))
166
+ .add (createTrailerStream ())
167
+ .add (CRLF )
168
+ .build ();
169
+
170
+ return Chunk .create (chunkData , chunkData .size );
165
171
}
166
172
167
- private void writeChunk (byte [] chunk , ByteArrayOutputStream outputStream ) throws IOException {
168
- writeHeader (chunk , outputStream );
169
- writeExtensions (chunk , outputStream );
170
- outputStream .write (CRLF );
171
- outputStream .write (chunk );
173
+ private LengthAwareSequenceInputStream createChunkStream (ByteBuffer chunkData ) {
174
+ return LengthAwareSequenceInputStream .builder ()
175
+ .add (createHeaderStream (chunkData .asReadOnlyBuffer ()))
176
+ .add (createExtensionsStream (chunkData .asReadOnlyBuffer ()))
177
+ .add (CRLF )
178
+ .add (new ByteArrayInputStream (chunkData .array (),
179
+ chunkData .arrayOffset (),
180
+ chunkData .remaining ()))
181
+ .build ();
172
182
}
173
183
174
- private void writeHeader (byte [] chunk , ByteArrayOutputStream outputStream ) throws IOException {
175
- byte [] hdr = header .get (chunk );
176
- outputStream .write (hdr );
184
+ private ByteArrayInputStream createHeaderStream (ByteBuffer chunkData ) {
185
+ return new ByteArrayInputStream (header .get (chunkData ));
177
186
}
178
187
179
- private void writeExtensions (byte [] chunk , ByteArrayOutputStream outputStream ) throws IOException {
188
+ private LengthAwareSequenceInputStream createExtensionsStream (ByteBuffer chunkData ) {
189
+ LengthAwareSequenceInputStream .Builder result = LengthAwareSequenceInputStream .builder ();
180
190
for (ChunkExtensionProvider chunkExtensionProvider : extensions ) {
181
- Pair <byte [], byte []> ext = chunkExtensionProvider .get (chunk );
182
- outputStream . write (( byte ) ';' );
183
- outputStream . write (ext .left ());
184
- outputStream . write (( byte ) '=' );
185
- outputStream . write (ext .right ());
191
+ Pair <byte [], byte []> ext = chunkExtensionProvider .get (chunkData );
192
+ result . add ( SEMICOLON );
193
+ result . add (ext .left ());
194
+ result . add ( EQUALS );
195
+ result . add (ext .right ());
186
196
}
197
+ return result .build ();
187
198
}
188
199
189
- private void writeTrailers (ByteArrayOutputStream outputStream ) throws IOException {
200
+ private LengthAwareSequenceInputStream createTrailerStream () throws IOException {
201
+ LengthAwareSequenceInputStream .Builder result = LengthAwareSequenceInputStream .builder ();
190
202
for (TrailerProvider trailer : trailers ) {
191
203
Pair <String , List <String >> tlr = trailer .get ();
192
- outputStream .write (tlr .left ().getBytes (StandardCharsets .UTF_8 ));
193
- outputStream .write ((byte ) ':' );
194
- outputStream .write (String .join ("," , tlr .right ()).getBytes (StandardCharsets .UTF_8 ));
195
- outputStream .write (CRLF );
204
+ result .add (tlr .left ().getBytes (StandardCharsets .UTF_8 ));
205
+ result .add (COLON );
206
+ for (String trailerValue : tlr .right ()) {
207
+ result .add (trailerValue .getBytes (StandardCharsets .UTF_8 ));
208
+ result .add (COMMA );
209
+ }
210
+
211
+ // Replace trailing comma with clrf
212
+ result .replaceLast (new ByteArrayInputStream (CRLF ), COMMA .length );
196
213
}
214
+ return result .build ();
197
215
}
198
216
199
217
@ Override
@@ -216,7 +234,8 @@ public static class Builder {
216
234
private final List <TrailerProvider > trailers = new ArrayList <>();
217
235
private InputStream inputStream ;
218
236
private int chunkSize ;
219
- private ChunkHeaderProvider header = chunk -> Integer .toHexString (chunk .length ).getBytes (StandardCharsets .UTF_8 );
237
+ private ChunkHeaderProvider header =
238
+ chunk -> Integer .toHexString (chunk .remaining ()).getBytes (StandardCharsets .UTF_8 );
220
239
221
240
public InputStream inputStream () {
222
241
return this .inputStream ;
@@ -267,5 +286,51 @@ public ChunkedEncodedInputStream build() {
267
286
return new ChunkedEncodedInputStream (this );
268
287
}
269
288
}
289
+
290
+
291
+ private static class LengthAwareSequenceInputStream extends SequenceInputStream {
292
+ private final int size ;
293
+
294
+ private LengthAwareSequenceInputStream (Builder builder ) {
295
+ super (Collections .enumeration (builder .streams ));
296
+ this .size = builder .size ;
297
+ }
298
+
299
+ private static Builder builder () {
300
+ return new Builder ();
301
+ }
302
+
303
+ private static class Builder {
304
+ private final List <InputStream > streams = new ArrayList <>();
305
+ private int size = 0 ;
306
+
307
+ public Builder add (ByteArrayInputStream stream ) {
308
+ streams .add (stream );
309
+ size += stream .available ();
310
+ return this ;
311
+ }
312
+
313
+ public Builder add (byte [] stream ) {
314
+ return add (new ByteArrayInputStream (stream ));
315
+ }
316
+
317
+ public Builder add (LengthAwareSequenceInputStream stream ) {
318
+ streams .add (stream );
319
+ size += stream .size ;
320
+ return this ;
321
+ }
322
+
323
+ public Builder replaceLast (ByteArrayInputStream stream , int lastLength ) {
324
+ streams .set (streams .size () - 1 , stream );
325
+ size -= lastLength ;
326
+ size += stream .available ();
327
+ return this ;
328
+ }
329
+
330
+ public LengthAwareSequenceInputStream build () {
331
+ return new LengthAwareSequenceInputStream (this );
332
+ }
333
+ }
334
+ }
270
335
}
271
336
0 commit comments