4
4
5
5
use AsyncAws \Core \Credentials \Credentials ;
6
6
use AsyncAws \Core \Exception \InvalidArgument ;
7
- use AsyncAws \Core \Exception \RuntimeException ;
7
+ use AsyncAws \Core \Stream \FixedSizeStream ;
8
+ use AsyncAws \Core \Stream \IterableStream ;
9
+ use AsyncAws \Core \Stream \Stream ;
10
+ use AsyncAws \Core \Stream \StringStream ;
8
11
9
12
/**
10
13
* Version4 of signer.
@@ -54,14 +57,7 @@ public function __construct(string $scopeName, string $region)
54
57
55
58
public function sign (Request $ request , ?Credentials $ credentials ): void
56
59
{
57
- $ body = $ request ->getBody () ?? '' ;
58
- if (\is_resource ($ body ) && -1 === fseek ($ body , 0 )) {
59
- throw new RuntimeException ('Unable to seek the resource ' );
60
- }
61
-
62
60
if (null === $ credentials ) {
63
- $ request ->setHeader ('content-length ' , $ this ->getContentLength ($ request ));
64
-
65
61
return ;
66
62
}
67
63
@@ -86,30 +82,22 @@ public function sign(Request $request, ?Credentials $credentials): void
86
82
87
83
$ canonicalHeaders = $ this ->getCanonicalizedHeaders ($ request );
88
84
89
- $ canonicalRequest = implode (
90
- "\n" ,
91
- [
92
- $ request ->getMethod (),
93
- $ this ->getCanonicalizedPath ($ parsedUrl ),
94
- $ this ->getCanonicalizedQuery ($ parsedUrl ),
95
- \implode ("\n" , array_values ($ canonicalHeaders )),
96
- '' , // empty line after headers
97
- implode ('; ' , \array_keys ($ canonicalHeaders )),
98
- $ request ->getHeader ('x-amz-content-sha256 ' ),
99
- ]
100
- );
101
-
102
- $ stringToSign = implode (
103
- "\n" ,
104
- [
105
- self ::ALGORITHM_REQUEST ,
106
- $ amzDate ,
107
- implode ('/ ' , $ credentialScope ),
108
- hash ('sha256 ' , $ canonicalRequest ),
109
- ]
110
- );
85
+ $ canonicalRequest = implode ("\n" , [
86
+ $ request ->getMethod (),
87
+ $ this ->getCanonicalizedPath ($ parsedUrl ),
88
+ $ this ->getCanonicalizedQuery ($ parsedUrl ),
89
+ \implode ("\n" , array_values ($ canonicalHeaders )),
90
+ '' , // empty line after headers
91
+ implode ('; ' , \array_keys ($ canonicalHeaders )),
92
+ $ request ->getHeader ('x-amz-content-sha256 ' ),
93
+ ]);
111
94
112
- $ signature = hash_hmac ('sha256 ' , $ stringToSign , $ signingKey );
95
+ $ signature = hash_hmac ('sha256 ' , implode ("\n" , [
96
+ self ::ALGORITHM_REQUEST ,
97
+ $ amzDate ,
98
+ implode ('/ ' , $ credentialScope ),
99
+ hash ('sha256 ' , $ canonicalRequest ),
100
+ ]), $ signingKey );
113
101
114
102
$ authorizationHeader = sprintf (
115
103
'%s Credential=%s/%s, SignedHeaders=%s, Signature=%s ' ,
@@ -123,122 +111,69 @@ public function sign(Request $request, ?Credentials $credentials): void
123
111
$ request ->setHeader ('authorization ' , $ authorizationHeader );
124
112
}
125
113
126
- private function getContentLength (Request $ request ): ? int
114
+ private function prepareBody (Request $ request, string $ amzDate , string $ credentialScope , string & $ signature , string $ signingKey ): void
127
115
{
128
- if ($ request ->hasHeader ('content-length ' )) {
129
- return (int ) ((array ) $ request ->getHeader ('content-length ' ))[0 ];
130
- }
131
-
132
116
$ body = $ request ->getBody ();
133
- if (\is_string ($ body )) {
134
- return \strlen ($ body );
135
- }
136
117
137
- if (\is_resource ($ body )) {
138
- return fstat ($ body )['size ' ] ?? null ;
118
+ if ($ request ->hasHeader ('content-length ' )) {
119
+ $ contentLength = (int ) ((array ) $ request ->getHeader ('content-length ' ))[0 ];
120
+ } else {
121
+ $ contentLength = $ body ->length ();
139
122
}
140
123
141
- return null ;
142
- }
143
-
144
- private function prepareBody (Request $ request , string $ amzDate , string $ credentialScope , string &$ signature , string $ signingKey ): void
145
- {
146
- $ body = $ request ->getBody ();
147
-
148
- // we can't manage signature of undefined length closure
149
- $ contentLength = $ this ->getContentLength ($ request );
124
+ // we can't manage signature of undefined length. Let's convert it to string
150
125
if (null === $ contentLength ) {
151
- if (\is_callable ($ body )) {
152
- $ buffer = '' ;
153
- while (true ) {
154
- if (!\is_string ($ data = $ body (self ::CHUNK_SIZE ))) {
155
- throw new InvalidArgument (sprintf ('The return value of the "body" option callback must be a string, %s returned. ' , \gettype ($ data )));
156
- }
157
-
158
- if ('' == $ data ) {
159
- break ;
160
- }
161
-
162
- $ buffer .= $ data ;
163
- }
164
-
165
- $ request ->setBody ($ body = $ buffer );
166
- } elseif (\is_resource ($ body )) {
167
- $ request ->setBody ($ body = \stream_get_contents ($ body ));
168
- }
126
+ $ request ->setBody ($ body = StringStream::create ($ body ));
127
+ $ contentLength = $ body ->length ();
169
128
}
170
129
171
- if (\is_string ($ body )) {
172
- $ request ->setHeader ('x-amz-content-sha256 ' , hash ('sha256 ' , $ body ));
130
+ // no need to stream small body
131
+ if ($ contentLength < self ::CHUNK_SIZE ) {
132
+ $ request ->setBody ($ body = StringStream::create ($ body ));
133
+ $ request ->setHeader ('x-amz-content-sha256 ' , hash ('sha256 ' , $ body ->stringify ()));
173
134
$ request ->setHeader ('content-length ' , $ contentLength );
174
135
175
136
return ;
176
137
}
177
138
178
- $ streamReader = null ;
179
- if (\is_callable ($ body )) {
180
- $ eof = false ;
181
- $ buffer = '' ;
182
- $ streamReader = static function () use ($ body , &$ buffer , &$ eof ): string {
183
- while (!$ eof && \strlen ($ buffer ) < self ::CHUNK_SIZE ) {
184
- if (!\is_string ($ data = $ body (self ::CHUNK_SIZE ))) {
185
- throw new InvalidArgument (sprintf ('The return value of the "body" option callback must be a string, %s returned. ' , \gettype ($ data )));
186
- }
187
-
188
- $ buffer .= $ data ;
189
- $ eof = '' === $ data ;
190
- }
191
-
192
- $ data = substr ($ buffer , 0 , self ::CHUNK_SIZE );
193
- $ buffer = substr ($ buffer , self ::CHUNK_SIZE );
194
-
195
- return $ data ;
196
- };
197
- } elseif (\is_resource ($ body )) {
198
- $ streamReader = static function () use ($ body ): string {
199
- return \fread ($ body , self ::CHUNK_SIZE );
200
- };
201
- }
202
- if (null === $ contentLength ) {
203
- throw new RuntimeException ('Unable to get resource size ' );
204
- }
205
- if (null === $ streamReader ) {
206
- throw new InvalidArgument (\sprintf ('Unexpected body "%s". ' , \is_object ($ body ) ? \get_class ($ body ) : \gettype ($ body )));
207
- }
208
-
209
139
$ request ->setHeader ('content-encoding ' , 'aws-chunked ' );
210
140
$ request ->setHeader ('x-amz-decoded-content-length ' , $ contentLength );
211
141
$ request ->setHeader ('x-amz-content-sha256 ' , 'STREAMING- ' . self ::ALGORITHM_CHUNK );
212
142
143
+ // Compute size of content + metadata used sign each Chunk
213
144
$ chunkCount = (int ) ceil ($ contentLength / self ::CHUNK_SIZE );
214
145
$ fullChunkCount = $ chunkCount * self ::CHUNK_SIZE === $ contentLength ? $ chunkCount : ($ chunkCount - 1 );
215
-
216
146
$ metaLength = \strlen (";chunk-signature= \r\n\r\n" ) + 64 ;
217
- $ contentLength = $ contentLength + $ fullChunkCount * ($ metaLength + \strlen ((string ) dechex (self ::CHUNK_SIZE ))) + ($ chunkCount - $ fullChunkCount ) * ($ metaLength + \strlen ((string ) dechex ($ contentLength % self ::CHUNK_SIZE ))) + $ metaLength + 1 ;
218
- $ request ->setHeader ('content-length ' , $ contentLength );
219
-
220
- $ last = false ;
221
- $ streamBody = static function () use ($ streamReader , $ amzDate , $ credentialScope , &$ signature , &$ last , $ signingKey ): string {
222
- if ($ last ) {
223
- return '' ;
224
- }
225
-
226
- if ('' === $ data = $ streamReader ()) {
227
- $ last = true ;
147
+ $ request ->setHeader ('content-length ' , $ contentLength + $ fullChunkCount * ($ metaLength + \strlen ((string ) dechex (self ::CHUNK_SIZE ))) + ($ chunkCount - $ fullChunkCount ) * ($ metaLength + \strlen ((string ) dechex ($ contentLength % self ::CHUNK_SIZE ))) + $ metaLength + 1 );
148
+
149
+ $ body = IterableStream::create ((static function (Stream $ body ) use ($ amzDate , $ credentialScope , $ signingKey , &$ signature ): iterable {
150
+ $ emptyHash = hash ('sha256 ' , '' );
151
+ foreach (FixedSizeStream::create ($ body , self ::CHUNK_SIZE ) as $ chunk ) {
152
+ $ signature = hash_hmac ('sha256 ' , implode ("\n" , [
153
+ self ::ALGORITHM_CHUNK ,
154
+ $ amzDate ,
155
+ $ credentialScope ,
156
+ $ signature ,
157
+ $ emptyHash ,
158
+ hash ('sha256 ' , $ chunk ),
159
+ ]), $ signingKey );
160
+
161
+ yield sprintf ("%s;chunk-signature=%s \r\n" , dechex (\strlen ($ chunk )), $ signature ) . "$ chunk \r\n" ;
228
162
}
229
163
230
- $ stringToSign = implode ("\n" , [
164
+ $ signature = hash_hmac ( ' sha256 ' , implode ("\n" , [
231
165
self ::ALGORITHM_CHUNK ,
232
166
$ amzDate ,
233
167
$ credentialScope ,
234
168
$ signature ,
235
- hash ('sha256 ' , '' ),
236
- hash ('sha256 ' , $ data ),
237
- ]);
169
+ $ emptyHash ,
170
+ $ emptyHash ,
171
+ ]), $ signingKey );
172
+
173
+ yield sprintf ("%s;chunk-signature=%s \r\n\r\n" , dechex (0 ), $ signature );
174
+ })($ body ));
238
175
239
- return sprintf ('%s;chunk-signature=%s ' . "\r\n" , dechex (\strlen ($ data )), $ signature = hash_hmac ('sha256 ' , $ stringToSign , $ signingKey )) . $ data . "\r\n" ;
240
- };
241
- $ request ->setBody ($ streamBody );
176
+ $ request ->setBody ($ body );
242
177
}
243
178
244
179
private function getCanonicalizedQuery (array $ parseUrl ): string
0 commit comments