2525import org .reactivestreams .Publisher ;
2626import org .reactivestreams .Subscriber ;
2727import software .amazon .awssdk .annotations .SdkInternalApi ;
28+ import software .amazon .awssdk .http .auth .aws .internal .signer .io .ContentLengthAwareSubscriber ;
2829import software .amazon .awssdk .utils .Pair ;
2930import software .amazon .awssdk .utils .async .AddingTrailingDataSubscriber ;
3031import software .amazon .awssdk .utils .async .DelegatingSubscriber ;
3334
3435/**
3536 * An implementation of chunk-transfer encoding, but by wrapping a {@link Publisher} of {@link ByteBuffer}. This implementation
36- * supports chunk-headers, chunk-extensions.
37+ * supports chunk-headers, chunk-extensions, and trailer-part .
3738 * <p>
3839 * Per <a href="https://datatracker.ietf.org/doc/html/rfc7230#section-4.1">RFC-7230</a>, a chunk-transfer encoded message is
3940 * defined as:
@@ -66,6 +67,7 @@ public class ChunkedEncodedPublisher implements Publisher<ByteBuffer> {
6667 private static final byte COMMA = ',' ;
6768
6869 private final Publisher <ByteBuffer > wrapped ;
70+ private final long contentLength ;
6971 private final List <ChunkExtensionProvider > extensions = new ArrayList <>();
7072 private final List <TrailerProvider > trailers = new ArrayList <>();
7173 private final int chunkSize ;
@@ -74,6 +76,7 @@ public class ChunkedEncodedPublisher implements Publisher<ByteBuffer> {
7476
7577 public ChunkedEncodedPublisher (Builder b ) {
7678 this .wrapped = b .publisher ;
79+ this .contentLength = b .contentLength ;
7780 this .chunkSize = b .chunkSize ;
7881 this .extensions .addAll (b .extensions );
7982 this .trailers .addAll (b .trailers );
@@ -82,7 +85,8 @@ public ChunkedEncodedPublisher(Builder b) {
8285
8386 @ Override
8487 public void subscribe (Subscriber <? super ByteBuffer > subscriber ) {
85- Publisher <Iterable <ByteBuffer >> chunked = chunk (wrapped );
88+ Publisher <ByteBuffer > lengthEnforced = limitLength (wrapped , contentLength );
89+ Publisher <Iterable <ByteBuffer >> chunked = chunk (lengthEnforced );
8690 Publisher <Iterable <ByteBuffer >> trailingAdded = addTrailingChunks (chunked );
8791 Publisher <ByteBuffer > flattened = flatten (trailingAdded );
8892 Publisher <ByteBuffer > encoded = map (flattened , this ::encodeChunk );
@@ -111,6 +115,10 @@ private Iterable<Iterable<ByteBuffer>> getTrailingChunks() {
111115 return Collections .singletonList (trailing );
112116 }
113117
118+ private Publisher <ByteBuffer > limitLength (Publisher <ByteBuffer > publisher , long length ) {
119+ return subscriber -> publisher .subscribe (new ContentLengthAwareSubscriber (subscriber , length ));
120+ }
121+
114122 private Publisher <Iterable <ByteBuffer >> chunk (Publisher <ByteBuffer > upstream ) {
115123 return subscriber -> {
116124 upstream .subscribe (new ChunkingSubscriber (subscriber ));
@@ -153,8 +161,7 @@ private ByteBuffer encodeChunk(ByteBuffer byteBuffer) {
153161 }
154162
155163 int trailerLen = trailerData .stream ()
156- // + 2 for each CRLF that ends the header-field
157- .mapToInt (t -> t .remaining () + 2 )
164+ .mapToInt (t -> t .remaining () + CRLF .length )
158165 .sum ();
159166
160167 int encodedLen = chunkSizeHex .length + extensionsLength + CRLF .length + contentLen + trailerLen + CRLF .length ;
@@ -188,11 +195,11 @@ private ByteBuffer encodeChunk(ByteBuffer byteBuffer) {
188195 encoded .put (t );
189196 encoded .put (CRLF );
190197 });
198+ // empty line ends the request body
191199 encoded .put (CRLF );
192200 }
193201
194202 encoded .flip ();
195-
196203 return encoded ;
197204 }
198205
@@ -294,6 +301,7 @@ public void onNext(ByteBuffer byteBuffer) {
294301
295302 public static class Builder {
296303 private Publisher <ByteBuffer > publisher ;
304+ private long contentLength ;
297305 private int chunkSize ;
298306 private boolean addEmptyTrailingChunk ;
299307 private final List <ChunkExtensionProvider > extensions = new ArrayList <>();
@@ -304,6 +312,15 @@ public Builder publisher(Publisher<ByteBuffer> publisher) {
304312 return this ;
305313 }
306314
315+ public Publisher <ByteBuffer > publisher () {
316+ return publisher ;
317+ }
318+
319+ public Builder contentLength (long contentLength ) {
320+ this .contentLength = contentLength ;
321+ return this ;
322+ }
323+
307324 public Builder chunkSize (int chunkSize ) {
308325 this .chunkSize = chunkSize ;
309326 return this ;
@@ -324,6 +341,10 @@ public Builder addTrailer(TrailerProvider trailerProvider) {
324341 return this ;
325342 }
326343
344+ public List <TrailerProvider > trailers () {
345+ return trailers ;
346+ }
347+
327348 public ChunkedEncodedPublisher build () {
328349 return new ChunkedEncodedPublisher (this );
329350 }
0 commit comments