1414package io .trino .aws .proxy .server .rest ;
1515
1616import com .google .common .base .Splitter ;
17+ import com .google .common .collect .ImmutableList ;
1718import io .trino .aws .proxy .spi .signing .ChunkSigningSession ;
1819import jakarta .ws .rs .WebApplicationException ;
1920
2021import java .io .IOException ;
2122import java .io .InputStream ;
23+ import java .nio .charset .StandardCharsets ;
2224import java .util .List ;
2325import java .util .Map ;
2426import java .util .Optional ;
@@ -30,7 +32,7 @@ class AwsChunkedInputStream
3032 extends InputStream
3133{
3234 private final InputStream delegate ;
33- private final ChunkSigningSession chunkSigningSession ;
35+ private final Optional < ChunkSigningSession > chunkSigningSession ;
3436
3537 private enum State
3638 {
@@ -44,12 +46,14 @@ private enum State
4446 private int bytesRemainingInChunk ;
4547 private int bytesAccountedFor ;
4648 private final int decodedContentLength ;
49+ private final List <String > trailerHeaders ;
4750
48- AwsChunkedInputStream (InputStream delegate , ChunkSigningSession chunkSigningSession , int decodedContentLength )
51+ AwsChunkedInputStream (InputStream delegate , Optional < ChunkSigningSession > chunkSigningSession , int decodedContentLength , List < String > trailerHeaders )
4952 {
5053 this .delegate = requireNonNull (delegate , "delegate is null" );
5154 this .chunkSigningSession = requireNonNull (chunkSigningSession , "chunkSigningSession is null" );
5255 this .decodedContentLength = decodedContentLength ;
56+ this .trailerHeaders = requireNonNull (ImmutableList .copyOf (trailerHeaders ), "trailerHeaders is null" );
5357 }
5458
5559 @ Override
@@ -65,7 +69,7 @@ public int read()
6569 throw new WebApplicationException ("Unexpected end of stream" , BAD_REQUEST );
6670 }
6771
68- chunkSigningSession .write ((byte ) (i & 0xff ));
72+ chunkSigningSession .ifPresent ( chunkSigningSession -> chunkSigningSession . write ((byte ) (i & 0xff ) ));
6973 updateBytesRemaining (1 );
7074
7175 return i ;
@@ -86,7 +90,7 @@ public int read(byte[] b, int off, int len)
8690 throw new WebApplicationException ("Unexpected end of stream" , BAD_REQUEST );
8791 }
8892
89- chunkSigningSession .write (b , off , count );
93+ chunkSigningSession .ifPresent ( chunkSigningSession -> chunkSigningSession . write (b , off , count ) );
9094 updateBytesRemaining (count );
9195
9296 return count ;
@@ -155,9 +159,6 @@ private void nextChunk()
155159 boolean success = false ;
156160 do {
157161 List <String > parts = Splitter .on (';' ).trimResults ().limit (2 ).splitToList (header );
158- if (parts .size () != 2 ) {
159- break ;
160- }
161162
162163 int chunkSize ;
163164 try {
@@ -170,23 +171,41 @@ private void nextChunk()
170171 break ;
171172 }
172173
173- Optional <String > chunkSignature = Splitter .on (';' ).trimResults ().withKeyValueSeparator ('=' ).split (parts .get (1 ))
174- .entrySet ()
175- .stream ()
176- .filter (entry -> entry .getKey ().equalsIgnoreCase ("chunk-signature" ))
177- .map (Map .Entry ::getValue )
178- .findFirst ();
174+ if (chunkSigningSession .isPresent ()) {
175+ if (parts .size () != 2 ) {
176+ break ;
177+ }
178+
179+ Optional <String > chunkSignature = Splitter .on (';' ).trimResults ().withKeyValueSeparator ('=' ).split (parts .get (1 ))
180+ .entrySet ()
181+ .stream ()
182+ .filter (entry -> entry .getKey ().equalsIgnoreCase ("chunk-signature" ))
183+ .map (Map .Entry ::getValue )
184+ .findFirst ();
179185
180- if (chunkSignature .isEmpty ()) {
181- break ;
186+ if (chunkSignature .isEmpty ()) {
187+ break ;
188+ }
189+
190+ chunkSigningSession .get ().startChunk (chunkSignature .get ());
191+ }
192+ else {
193+ if (parts .size () != 1 ) {
194+ break ;
195+ }
182196 }
183197
184- chunkSigningSession .startChunk (chunkSignature .get ());
185198 bytesRemainingInChunk = chunkSize ;
186199
187200 if (chunkSize == 0 ) {
188- readEmptyLine ();
189- chunkSigningSession .complete ();
201+ if (trailerHeaders .isEmpty ()) {
202+ readEmptyLine ();
203+ chunkSigningSession .ifPresent (ChunkSigningSession ::complete );
204+ }
205+ else {
206+ readTrailingHeaders ();
207+ readEmptyLine ();
208+ }
190209 state = State .LAST_CHUNK ;
191210 }
192211 bytesAccountedFor += chunkSize ;
@@ -236,4 +255,44 @@ private String readLine()
236255
237256 return line .toString ();
238257 }
258+
259+ private TrailerHeaderChunk readTrailingHeadersChunk ()
260+ throws IOException
261+ {
262+ Optional <String > signature = Optional .empty ();
263+ StringBuilder trailerHeadersChunkBuilder = new StringBuilder ();
264+ for (int i = 0 ; i < this .trailerHeaders .size (); i ++) {
265+ String trailerHeaders = readLine ();
266+ List <String > trailerHeadersValues = Splitter .on (":" ).trimResults ().limit (2 ).splitToList (trailerHeaders );
267+ String trailerHeaderName = trailerHeadersValues .getFirst ();
268+ if ((trailerHeadersValues .size () != 2 ) || !this .trailerHeaders .contains (trailerHeaderName )) {
269+ throw new WebApplicationException ("Trailer header is invalid: " + trailerHeaders , BAD_REQUEST );
270+ }
271+ if (trailerHeaderName .equals ("x-amz-trailer-signature" )) {
272+ signature = Optional .of (trailerHeadersValues .getLast ());
273+ break ;
274+ }
275+ else {
276+ trailerHeadersChunkBuilder .append (trailerHeaders );
277+ }
278+ }
279+ return new TrailerHeaderChunk (trailerHeadersChunkBuilder .toString (), signature );
280+ }
281+
282+ private void readTrailingHeaders ()
283+ throws IOException
284+ {
285+ TrailerHeaderChunk trailerHeaderChunk = readTrailingHeadersChunk ();
286+ chunkSigningSession .ifPresent (chunkSigningSession -> {
287+ if (trailerHeaderChunk .signature .isEmpty ()) {
288+ throw new WebApplicationException ("Expected x-amz-trailer-signature, none found" , BAD_REQUEST );
289+ }
290+ chunkSigningSession .startChunk (trailerHeaderChunk .signature .get ());
291+ byte [] trailerHeaderContent = trailerHeaderChunk .trailerHeaders .getBytes (StandardCharsets .UTF_8 );
292+ chunkSigningSession .write (trailerHeaderContent , 0 , trailerHeaderContent .length );
293+ chunkSigningSession .complete ();
294+ });
295+ }
296+
297+ private record TrailerHeaderChunk (String trailerHeaders , Optional <String > signature ) {}
239298}
0 commit comments