66use AsyncAws \Core \Exception \InvalidArgument ;
77use AsyncAws \Core \Request ;
88use AsyncAws \Core \RequestContext ;
9- use AsyncAws \Core \Stream \FixedSizeStream ;
10- use AsyncAws \Core \Stream \IterableStream ;
119use AsyncAws \Core \Stream \ReadOnceResultStream ;
12- use AsyncAws \Core \Stream \RequestStream ;
1310use AsyncAws \Core \Stream \RewindableStream ;
1411use AsyncAws \Core \Stream \StringStream ;
1512
2118class SignerV4 implements Signer
2219{
2320 private const ALGORITHM_REQUEST = 'AWS4-HMAC-SHA256 ' ;
24- private const ALGORITHM_CHUNK = self ::ALGORITHM_REQUEST . '-PAYLOAD ' ;
25- private const CHUNK_SIZE = 64 * 1024 ;
2621
2722 private const BLACKLIST_HEADERS = [
2823 'cache-control ' => true ,
@@ -101,6 +96,12 @@ protected function buildBodyDigest(Request $request, bool $isPresign): string
10196 return $ hash ;
10297 }
10398
99+ protected function convertBodyToStream (SigningContext $ context ): void
100+ {
101+ $ request = $ context ->getRequest ();
102+ $ request ->setBody (StringStream::create ($ request ->getBody ()));
103+ }
104+
104105 private function handleSignature (Request $ request , Credentials $ credentials , \DateTimeImmutable $ now , \DateTimeImmutable $ expires , bool $ isPresign ): void
105106 {
106107 $ this ->removePresign ($ request );
@@ -109,16 +110,17 @@ private function handleSignature(Request $request, Credentials $credentials, \Da
109110
110111 $ this ->buildTime ($ request , $ now , $ expires , $ isPresign );
111112 $ credentialScope = $ this ->buildCredentialString ($ request , $ credentials , $ now , $ isPresign );
112- $ credentialString = \implode ('/ ' , $ credentialScope );
113- $ signingKey = $ this ->buildSigningKey ($ credentials , $ credentialScope );
114-
113+ $ context = new SigningContext (
114+ $ request ,
115+ $ now ,
116+ \implode ('/ ' , $ credentialScope ),
117+ $ this ->buildSigningKey ($ credentials , $ credentialScope )
118+ );
115119 if ($ isPresign ) {
116120 // Should be called before `buildBodyDigest` because this method may alter the body
117121 $ this ->convertBodyToQuery ($ request );
118122 } else {
119- // $signature does not exists but passed by reference then computed buildSignature
120- $ signature = '' ;
121- $ this ->convertBodyToStream ($ request , $ now , $ credentialString , $ signingKey , $ signature );
123+ $ this ->convertBodyToStream ($ context );
122124 }
123125
124126 $ bodyDigest = $ this ->buildBodyDigest ($ request , $ isPresign );
@@ -130,8 +132,8 @@ private function handleSignature(Request $request, Credentials $credentials, \Da
130132
131133 $ canonicalHeaders = $ this ->buildCanonicalHeaders ($ request , $ isPresign );
132134 $ canonicalRequest = $ this ->buildCanonicalRequest ($ request , $ canonicalHeaders , $ bodyDigest );
133- $ stringToSign = $ this ->buildStringToSign ($ now , $ credentialString , $ canonicalRequest );
134- $ signature = $ this ->buildSignature ($ stringToSign , $ signingKey );
135+ $ stringToSign = $ this ->buildStringToSign ($ context -> getNow () , $ context -> getCredentialString () , $ canonicalRequest );
136+ $ context -> setSignature ( $ signature = $ this ->buildSignature ($ stringToSign , $ context -> getSigningKey ()) );
135137
136138 if ($ isPresign ) {
137139 $ request ->setQueryAttribute ('X-Amz-Signature ' , $ signature );
@@ -254,57 +256,6 @@ private function convertBodyToQuery(Request $request): void
254256 $ request ->setBody (StringStream::create ('' ));
255257 }
256258
257- private function convertBodyToStream (Request $ request , \DateTimeImmutable $ now , string $ credentialString , string $ signingKey , string &$ signature ): void
258- {
259- $ body = $ request ->getBody ();
260- if ($ request ->hasHeader ('content-length ' )) {
261- $ contentLength = (int ) $ request ->getHeader ('content-length ' );
262- } else {
263- $ contentLength = $ body ->length ();
264- }
265-
266- // If content length is unknown, use the rewindable stream to read it once locally in order to get the length
267- if (null === $ contentLength ) {
268- $ request ->setBody ($ body = RewindableStream::create ($ body ));
269- $ body ->read ();
270- $ contentLength = $ body ->length ();
271- }
272-
273- // no need to stream small body. It's simple to convert it to string directly
274- if ($ contentLength < self ::CHUNK_SIZE ) {
275- $ request ->setBody ($ body = StringStream::create ($ body ));
276-
277- return ;
278- }
279-
280- // Convert the body into a chunked stream
281- $ request ->setHeader ('content-encoding ' , 'aws-chunked ' );
282- $ request ->setHeader ('x-amz-decoded-content-length ' , (string ) $ contentLength );
283- $ request ->setHeader ('x-amz-content-sha256 ' , 'STREAMING- ' . self ::ALGORITHM_CHUNK );
284-
285- // Compute size of content + metadata used sign each Chunk
286- $ chunkCount = (int ) ceil ($ contentLength / self ::CHUNK_SIZE );
287- $ fullChunkCount = $ chunkCount * self ::CHUNK_SIZE === $ contentLength ? $ chunkCount : ($ chunkCount - 1 );
288- $ metaLength = \strlen (";chunk-signature= \r\n\r\n" ) + 64 ;
289- $ request ->setHeader ('content-length ' , (string ) ($ contentLength + $ fullChunkCount * ($ metaLength + \strlen ((string ) dechex (self ::CHUNK_SIZE ))) + ($ chunkCount - $ fullChunkCount ) * ($ metaLength + \strlen ((string ) dechex ($ contentLength % self ::CHUNK_SIZE ))) + $ metaLength + 1 ));
290-
291- $ body = IterableStream::create ((function (RequestStream $ body ) use ($ now , $ credentialString , $ signingKey , &$ signature ): iterable {
292- foreach (FixedSizeStream::create ($ body , self ::CHUNK_SIZE ) as $ chunk ) {
293- $ stringToSign = $ this ->buildChunkStringToSign ($ now , $ credentialString , $ signature , $ chunk );
294- $ signature = $ this ->buildSignature ($ stringToSign , $ signingKey );
295-
296- yield sprintf ("%s;chunk-signature=%s \r\n" , dechex (\strlen ($ chunk )), $ signature ) . "$ chunk \r\n" ;
297- }
298-
299- $ stringToSign = $ this ->buildChunkStringToSign ($ now , $ credentialString , $ signature , '' );
300- $ signature = $ this ->buildSignature ($ stringToSign , $ signingKey );
301-
302- yield sprintf ("%s;chunk-signature=%s \r\n\r\n" , dechex (0 ), $ signature );
303- })($ body ));
304-
305- $ request ->setBody ($ body );
306- }
307-
308259 private function buildCanonicalHeaders (Request $ request , bool $ isPresign ): array
309260 {
310261 // Case-insensitively aggregate all of the headers.
@@ -393,21 +344,6 @@ private function buildStringToSign(\DateTimeImmutable $now, string $credentialSt
393344 ]);
394345 }
395346
396- private function buildChunkStringToSign (\DateTimeImmutable $ now , string $ credentialString , string $ signature , string $ chunk ): string
397- {
398- static $ emptyHash ;
399- $ emptyHash = $ emptyHash ?? hash ('sha256 ' , '' );
400-
401- return implode ("\n" , [
402- self ::ALGORITHM_CHUNK ,
403- $ now ->format ('Ymd\THis\Z ' ),
404- $ credentialString ,
405- $ signature ,
406- $ emptyHash ,
407- hash ('sha256 ' , $ chunk ),
408- ]);
409- }
410-
411347 private function buildSigningKey (Credentials $ credentials , array $ credentialScope ): string
412348 {
413349 $ signingKey = 'AWS4 ' . $ credentials ->getSecretKey ();
0 commit comments