Skip to content

Commit a879c1a

Browse files
authored
Fix PreSign request (#358)
* Use DateTimte::format to generate date * Fix typo in comment * Use `php://temp` to deal with buffer * Simplify Stream factories in decorators * Move $signature in else branch * Switch to UTC
1 parent 389d611 commit a879c1a

File tree

3 files changed

+28
-40
lines changed

3 files changed

+28
-40
lines changed

src/Signer/SignerV4.php

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -61,15 +61,27 @@ public function __construct(string $scopeName, string $region)
6161

6262
public function presign(Request $request, Credentials $credentials, RequestContext $context): void
6363
{
64-
$now = $context->getCurrentDate() ?? new \DateTimeImmutable();
65-
$expires = $context->getExpirationDate() ?? (new \DateTimeImmutable($now->format(\DateTimeInterface::ATOM)))->add(new \DateInterval('PT1H'));
64+
if (null === $now = $context->getCurrentDate()) {
65+
$now = new \DateTimeImmutable();
66+
} else {
67+
$now = new \DateTimeImmutable($now->format(\DateTimeInterface::ATOM));
68+
}
69+
// Signer date have to be UTC https://docs.aws.amazon.com/general/latest/gr/sigv4-date-handling.html
70+
$now = $now->setTimezone(new \DateTimeZone('UTC'));
71+
$expires = $context->getExpirationDate() ?? $now->add(new \DateInterval('PT1H'));
6672

6773
$this->handleSignature($request, $credentials, $now, $expires, true);
6874
}
6975

7076
public function sign(Request $request, Credentials $credentials, RequestContext $context): void
7177
{
72-
$now = $context->getCurrentDate() ?? new \DateTimeImmutable();
78+
if (null === $now = $context->getCurrentDate()) {
79+
$now = new \DateTimeImmutable();
80+
} else {
81+
$now = new \DateTimeImmutable($now->format(\DateTimeInterface::ATOM));
82+
}
83+
// Signer date have to be UTC https://docs.aws.amazon.com/general/latest/gr/sigv4-date-handling.html
84+
$now = $now->setTimezone(new \DateTimeZone('UTC'));
7385

7486
$this->handleSignature($request, $credentials, $now, $now, false);
7587
}
@@ -105,19 +117,19 @@ private function handleSignature(Request $request, Credentials $credentials, \Da
105117
$credentialString = \implode('/', $credentialScope);
106118
$signingKey = $this->buildSigningKey($credentials, $credentialScope);
107119

108-
// signature is passed by reference to convertBodyToStream
109-
$signature = '';
110120
if ($isPresign) {
111121
// Should be called before `buildBodyDigest` because this method may alter the body
112122
$this->convertBodyToQuery($request);
113123
} else {
124+
// $signature does not exists but passed by reference then computed buildSignature
125+
$signature = '';
114126
$this->convertBodyToStream($request, $now, $credentialString, $signingKey, $signature);
115127
}
116128

117129
$bodyDigest = $this->buildBodyDigest($request, $isPresign);
118130

119131
if ($isPresign) {
120-
// Should be called after `buildBodyDigest` because header this method may remove `x-amz-content-sha256`
132+
// Should be called after `buildBodyDigest` because this method may remove the header `x-amz-content-sha256`
121133
$this->convertHeaderToQuery($request);
122134
}
123135

@@ -196,16 +208,16 @@ private function buildTime(Request $request, \DateTimeInterface $now, \DateTimeI
196208
throw new InvalidArgument('The expiration date of presigned URL must be in the future');
197209
}
198210

199-
$request->setQueryAttribute('X-Amz-Date', gmdate('Ymd\THis\Z', $now->getTimestamp()));
211+
$request->setQueryAttribute('X-Amz-Date', $now->format('Ymd\THis\Z'));
200212
$request->setQueryAttribute('X-Amz-Expires', $duration);
201213
} else {
202-
$request->setHeader('X-Amz-Date', gmdate('Ymd\THis\Z', $now->getTimestamp()));
214+
$request->setHeader('X-Amz-Date', $now->format('Ymd\THis\Z'));
203215
}
204216
}
205217

206218
private function buildCredentialString(Request $request, Credentials $credentials, \DateTimeInterface $now, bool $isPresign): array
207219
{
208-
$credentialScope = [gmdate('Ymd', $now->getTimestamp()), $this->region, $this->scopeName, 'aws4_request'];
220+
$credentialScope = [$now->format('Ymd'), $this->region, $this->scopeName, 'aws4_request'];
209221

210222
if ($isPresign) {
211223
$request->setQueryAttribute('X-Amz-Credential', $credentials->getAccessKeyId() . '/' . implode('/', $credentialScope));
@@ -380,7 +392,7 @@ private function buildStringToSign(\DateTimeInterface $now, string $credentialSt
380392
{
381393
return implode("\n", [
382394
self::ALGORITHM_REQUEST,
383-
gmdate('Ymd\THis\Z', $now->getTimestamp()),
395+
$now->format('Ymd\THis\Z'),
384396
$credentialString,
385397
hash('sha256', $canonicalRequest),
386398
]);
@@ -393,7 +405,7 @@ private function buildChunkStringToSign(\DateTimeInterface $now, string $credent
393405

394406
return implode("\n", [
395407
self::ALGORITHM_CHUNK,
396-
gmdate('Ymd\THis\Z', $now->getTimestamp()),
408+
$now->format('Ymd\THis\Z'),
397409
$credentialString,
398410
$signature,
399411
$emptyHash,

src/Stream/FixedSizeStream.php

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,13 @@ private function __construct(RequestStream $content, int $chunkSize = 64 * 1024)
2323
$this->chunkSize = $chunkSize;
2424
}
2525

26-
public static function create($content, int $chunkSize = 64 * 1024): FixedSizeStream
26+
public static function create(RequestStream $content, int $chunkSize = 64 * 1024): FixedSizeStream
2727
{
2828
if ($content instanceof self) {
2929
return $content;
3030
}
31-
if ($content instanceof RequestStream) {
32-
return new self($content, $chunkSize);
33-
}
3431

35-
throw new InvalidArgument(sprintf('Expect content to be an instance of "%s". "%s" given.', RequestStream::class, \is_object($content) ? \get_class($content) : \gettype($content)));
32+
return new self($content, $chunkSize);
3633
}
3734

3835
public function length(): ?int

src/Stream/RewindableStream.php

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
namespace AsyncAws\Core\Stream;
44

5-
use AsyncAws\Core\Exception\InvalidArgument;
6-
75
/**
86
* Provides a Stream that can be read several time.
97
*
@@ -31,16 +29,13 @@ private function __construct(RequestStream $content)
3129
$this->content = $content;
3230
}
3331

34-
public static function create($content): RewindableStream
32+
public static function create(RequestStream $content): RewindableStream
3533
{
3634
if ($content instanceof self) {
3735
return $content;
3836
}
39-
if ($content instanceof RequestStream) {
40-
return new self($content);
41-
}
4237

43-
throw new InvalidArgument(sprintf('Expect content to be a "Stream". "%s" given.', \is_object($content) ? \get_class($content) : \gettype($content)));
38+
return new self($content);
4439
}
4540

4641
public function length(): ?int
@@ -69,27 +64,11 @@ public function getIterator(): \Traversable
6964
return;
7065
}
7166

72-
$resource = \fopen('php://memory', 'r+b');
67+
$resource = \fopen('php://temp', 'r+b');
7368
$this->fallback = ResourceStream::create($resource);
7469

75-
$size = 0;
76-
$inmemory = true;
7770
foreach ($this->content as $chunk) {
7871
\fwrite($resource, $chunk);
79-
if ($inmemory) {
80-
$size += \strlen($chunk);
81-
// switch to filesystem if string larger that 1 Mb
82-
if ($size > 1024 * 1024) {
83-
$memoryStream = $resource;
84-
$resource = \tmpfile();
85-
$this->fallback = ResourceStream::create($resource);
86-
$inmemory = false;
87-
88-
\fseek($memoryStream, 0);
89-
\stream_copy_to_stream($memoryStream, $resource);
90-
\fclose($memoryStream);
91-
}
92-
}
9372
yield $chunk;
9473
}
9574
}

0 commit comments

Comments
 (0)