[s3_storage] Update Composer dependency ahead of release

- Updating akeeba/s3 (2.3.1 => 2.3.2)
This commit is contained in:
Hypolite Petovan 2024-03-19 23:03:34 -04:00
parent c6b2ed96d7
commit d910502a17
53 changed files with 583 additions and 206 deletions

View file

@ -3,7 +3,7 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/

View file

@ -3,7 +3,7 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
@ -82,6 +82,33 @@ class Configuration
*/
protected $endpoint = 's3.amazonaws.com';
/**
* Should I use an alternative date header format (D, d M Y H:i:s T instead of D, d M Y H:i:s O) for non-Amazon,
* S3-compatible services?
*
* This is enabled by default.
*
* @var bool
*/
protected $alternateDateHeaderFormat = true;
/**
* Should I use the standard HTTP Date header instead of the X-Amz-Date header?
*
* @var bool
*/
protected $useHTTPDateHeader = false;
/**
* Should pre-signed URLs include the bucket name in the URL? Only applies to v4 signatures.
*
* Amazon S3 and most implementations need this turned off (default). LocalStack seems to not work properly with the
* bucket name as a subdomain, hence the need for this flag.
*
* @var bool
*/
protected $preSignedBucketInURL = false;
/**
* Public constructor
*
@ -363,4 +390,58 @@ class Configuration
{
$this->useDualstackUrl = $useDualstackUrl;
}
/**
* Get the flag for using an alternate date format for non-Amazon, S3-compatible services.
*
* @return bool
*/
public function getAlternateDateHeaderFormat(): bool
{
return $this->alternateDateHeaderFormat;
}
/**
* Set the flag for using an alternate date format for non-Amazon, S3-compatible services.
*
* @param bool $alternateDateHeaderFormat
*
* @return void
*/
public function setAlternateDateHeaderFormat(bool $alternateDateHeaderFormat): void
{
$this->alternateDateHeaderFormat = $alternateDateHeaderFormat;
}
/**
* Get the flag indicating whether to use the HTTP Date header
*
* @return bool Flag indicating whether to use the HTTP Date header
*/
public function getUseHTTPDateHeader(): bool
{
return $this->useHTTPDateHeader;
}
/**
* Set the flag indicating whether to use the HTTP Date header.
*
* @param bool $useHTTPDateHeader Whether to use the HTTP Date header
*
* @return void
*/
public function setUseHTTPDateHeader(bool $useHTTPDateHeader): void
{
$this->useHTTPDateHeader = $useHTTPDateHeader;
}
public function getPreSignedBucketInURL(): bool
{
return $this->preSignedBucketInURL;
}
public function setPreSignedBucketInURL(bool $preSignedBucketInURL): void
{
$this->preSignedBucketInURL = $preSignedBucketInURL;
}
}

View file

@ -3,7 +3,7 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
@ -341,7 +341,7 @@ class Connector
* Authenticated (pre-signed) URLs are always made against the generic S3 region endpoint, not the bucket's
* virtual-hosting-style domain name. The bucket is always the first component of the path.
*
* For example, given a bucket called foobar and an object baz.txt in it we are pre-signing the URL
* For example, given a bucket called foobar, and an object baz.txt in it, we are pre-signing the URL
* https://s3-eu-west-1.amazonaws.com/foobar/baz.txt, not
* https://foobar.s3-eu-west-1.amazonaws.com/foobar/baz.txt (as we'd be doing with v2 signatures).
*
@ -354,7 +354,7 @@ class Connector
* object is true. Naturally, the default behavior being virtual-hosting-style access to buckets, this flag is
* most likely **false**.
*
* Therefore we need to clone the Configuration object, set the flag to true and create a Request object using
* Therefore, we need to clone the Configuration object, set the flag to true and create a Request object using
* the falsified Configuration object.
*
* Note that v2 signatures are not affected. In v2 we are always appending the bucket name to the path, despite
@ -368,7 +368,6 @@ class Connector
$newConfig->setUseLegacyPathStyle(true);
// Create the request object.
$uri = str_replace('%2F', '/', rawurlencode($uri));
$request = new Request('GET', $bucket, $uri, $newConfig);
if ($query)

View file

@ -3,7 +3,7 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/

View file

@ -3,7 +3,7 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/

View file

@ -3,7 +3,7 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/

View file

@ -3,7 +3,7 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/

View file

@ -3,7 +3,7 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/

View file

@ -3,7 +3,7 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/

View file

@ -3,7 +3,7 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/

View file

@ -3,7 +3,7 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/

View file

@ -3,7 +3,7 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/

View file

@ -3,7 +3,7 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/

View file

@ -3,7 +3,7 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/

View file

@ -3,7 +3,7 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/

View file

@ -3,7 +3,7 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/

View file

@ -3,7 +3,7 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/

View file

@ -3,7 +3,7 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/

View file

@ -3,7 +3,7 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/

View file

@ -3,7 +3,7 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/

View file

@ -3,7 +3,7 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
@ -143,7 +143,7 @@ class Request
$this->headers['Date'] = gmdate('D, d M Y H:i:s O');
// S3-"compatible" services use a different date format. Because why not?
if (strpos($this->headers['Host'], '.amazonaws.com') === false)
if ($this->configuration->getAlternateDateHeaderFormat() && strpos($this->headers['Host'], '.amazonaws.com') === false)
{
$this->headers['Date'] = gmdate('D, d M Y H:i:s T');
}
@ -438,16 +438,30 @@ class Request
curl_setopt($curl, CURLOPT_URL, $url);
/**
* Set the optional x-amz-date header for third party services.
* Set the optional x-amz-date header instead of the standard HTTP Date header.
*
* Amazon S3 proper expects to get the date from the Date header. Third party services typically implement the
* (wrongly) documented behaviour of using the x-amz-date header but, if it's missing, fall back to the Date
* header. Wasabi does not fall back; it only uses the x-amz-date header which is why we have to set it here if
* the request iss not made to Amazon S3 proper.
* Amazon S3 proper expects to get the date from the Date header. It also allows you to instead use the optional
* X-Amz-Date header as a means to resolve situations where your HTTP library does not let you control the
* standard Date header. In other words, it accepts both, it encourages the standard Date header, but it will
* give priority to the X-Amz-Date header to help you get out of a sticky situation.
*
* Unfortunately, third party services which claim to be "S3-compatible" are written by people with poor reading
* skills or, more likely, under unrealistic time constraints to deliver working code. They are implementing the
* date handling behaviout wrong. Instead of using the Date header unless the X-Amz-Date header is set, they
* **expect** to only ever see the X-Amz-Date header. If it's missing, they do not fall back to the standard
* Date header; they just spit out a message about the signature being wrong. Wasabi and ExoScale are two prime
* examples of that, and only when using v2 signatures.
*
* To avoid this problem, we are now defaulting to always using the X-Amz-Date header everywhere. If you want to
* revert to using the Date header with S3 proper please use setUseHTTPDateHeader(true) to your configuration
* object. In this case DO NOT set the X-Amz-Date header yourself, or you're going to have a *really* bad time.
*/
$this->headers['x-amz-date'] = strpos($this->headers['Host'], '.amazonaws.com') !== false
? ''
: (new \DateTime($this->headers['Date']))->format('Ymd\THis\Z');
if (!$this->configuration->getUseHTTPDateHeader())
{
$this->amzHeaders['x-amz-date'] = (new \DateTime($this->headers['Date']))->format('Ymd\THis\Z');
unset ($this->headers['Date']);
}
/**
* Remove empty headers.

View file

@ -3,7 +3,7 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/

View file

@ -3,7 +3,7 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/

View file

@ -3,7 +3,7 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/

View file

@ -3,7 +3,7 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
@ -64,10 +64,11 @@ class V2 extends Signature
$search = '/' . $bucket;
if (strpos($uri, $search) === 0)
{
$uri = substr($uri, strlen($search));
}
// This does not look right... The bucket name must be included in the URL.
// if (strpos($uri, $search) === 0)
// {
// $uri = substr($uri, strlen($search));
// }
$queryParameters = array_merge($this->request->getParameters(), [
'AWSAccessKeyId' => $accessKey,
@ -134,7 +135,15 @@ class V2 extends Signature
// See http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html#RESTAuthenticationQueryStringAuth
if (isset($headers['Expires']))
{
$headers['Date'] = $headers['Expires'];
if (isset($headers['Date']))
{
$headers['Date'] = $headers['Expires'];
}
else
{
$amzHeaders['x-amz-date'] = $headers['Expires'];
}
unset ($headers['Expires']);
$isPresignedURL = true;
@ -152,14 +161,14 @@ class V2 extends Signature
$stringToSign = $verb . "\n" .
($headers['Content-MD5'] ?? '') . "\n" .
($headers['Content-Type'] ?? '') . "\n" .
$headers['Date'] .
($headers['Date'] ?? '') .
$amzString . "\n" .
$resourcePath;
// CloudFront only requires a date to be signed
if ($headers['Host'] == 'cloudfront.amazonaws.com')
{
$stringToSign = $headers['Date'];
$stringToSign = $headers['Date'] ?? $amzHeaders['x-amz-date'] ?? '';
}
$amazonV2Hash = $this->amazonV2Hash($stringToSign);

View file

@ -3,7 +3,7 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
@ -80,7 +80,7 @@ class V4 extends Signature
$bucket = $this->request->getBucket();
$hostname = $this->getPresignedHostnameForRegion($region);
if ($this->isValidBucketName($bucket))
if (!$this->request->getConfiguration()->getPreSignedBucketInURL() && $this->isValidBucketName($bucket))
{
$hostname = $bucket . '.' . $hostname;
}
@ -99,7 +99,11 @@ class V4 extends Signature
// The query parameters are returned serialized; unserialize them, then build and return the URL.
$queryParameters = unserialize($serialisedParams);
if ($this->isValidBucketName($bucket) && strpos($uri, '/' . $bucket) === 0)
// This should be toggleable
if (
!$this->request->getConfiguration()->getPreSignedBucketInURL()
&& $this->isValidBucketName($bucket)
&& strpos($uri, '/' . $bucket) === 0)
{
$uri = substr($uri, strlen($bucket) + 1);
}
@ -137,7 +141,7 @@ class V4 extends Signature
}
// Get the credentials scope
$signatureDate = new DateTime($headers['Date']);
$signatureDate = new DateTime($headers['Date'] ?? $amzHeaders['x-amz-date']);
$credentialScope = $signatureDate->format('Ymd') . '/' .
$this->request->getConfiguration()->getRegion() . '/' .
@ -218,10 +222,37 @@ class V4 extends Signature
// The canonical URI is the resource path
$canonicalURI = $resourcePath;
$bucketResource = '/' . $bucket;
$bucketResource = '/' . $bucket . '/';
$regionalHostname = ($headers['Host'] != 's3.amazonaws.com')
&& ($headers['Host'] != $bucket . '.s3.amazonaws.com');
/**
* Yet another special case for third party, S3-compatible services, when using pre-signed URLs.
*
* Given a bucket `example` and filepath `foo/bar.txt` the canonical URI to sign is supposed to be
* /example/foo/bar.txt regardless of whether we are using path style or subdomain hosting style access to the
* bucket.
*
* When calculating a pre-signed URL, the URL we will be accessing will be something to the tune of
* example.endpoint.com/foo/bar.txt. Amazon S3 proper allows us to use EITHER the nominal canonical URI
* /foo/bar.txt OR the /example/foo/bar.txt canonical URI for consistency. Some third party providers, like
* Wasabi, will choke on the former and complain about the signature being invalid.
*
* To address this issue we check if all the following conditions are met:
* - We are calculating a signature for a pre-signed URL.
* - The service is NOT Amazon S3 proper.
* - The domain name starts with the bucket name.
* In this case, and this case only, we set $regionalHostname to false. This triggers an if-block further down
* which strips the `/bucketName/` prefix from the canonical URI, converting it to `/`. Therefore, the canonical
* URI in the signature becomes the nominal URI we will be accessing in the bucket, solving the problem with
* those third party services.
*/
// Figuring out whether it's a regional hostname DOES NOT work above if it's not AWS S3 proper. Let's fix that.
if ($isPresignedURL && strpos($headers['Host'], 'amazonaws.com') === false && !strpos($headers['Host'], $bucket . '.'))
{
$regionalHostname = false;
}
// Special case: if the canonical URI ends in /?location the bucket name DOES count as part of the canonical URL
// even though the Host is s3.amazonaws.com (in which case it normally shouldn't count). Yeah, I know, it makes
// no sense!!!
@ -231,15 +262,15 @@ class V4 extends Signature
$regionalHostname = true;
}
if (!$regionalHostname && (strpos($canonicalURI, $bucketResource) === 0))
if (!$regionalHostname && (strpos($canonicalURI, $bucketResource) === 0 || strpos($canonicalURI, substr($bucketResource, 0, -1)) === 0))
{
if ($canonicalURI === $bucketResource)
if ($canonicalURI === substr($bucketResource, 0, -1))
{
$canonicalURI = '/';
}
else
{
$canonicalURI = substr($canonicalURI, strlen($bucketResource));
$canonicalURI = substr($canonicalURI, strlen($bucketResource) - 1);
}
}
@ -323,7 +354,7 @@ class V4 extends Signature
* headers if the request is made to a service _other_ than Amazon S3 proper.
*/
$dateToSignFor = strpos($headers['Host'], '.amazonaws.com') !== false
? $headers['Date']
? (($headers['Date'] ?? null) ?: ($amzHeaders['x-amz-date'] ?? null) ?: $signatureDate->format('Ymd\THis\Z'))
: $signatureDate->format('Ymd\THis\Z');
$stringToSign = "AWS4-HMAC-SHA256\n" .
@ -410,7 +441,8 @@ class V4 extends Signature
$endpoint = 's3.' . $region . '.amazonaws.com';
}
$dualstackEnabled = $this->request->getConfiguration()->getDualstackUrl();
// As of October 2023, AWS does not consider DualStack signed URLs as valid. Whatever.
$dualstackEnabled = false && $this->request->getConfiguration()->getDualstackUrl();
// If dual-stack URLs are enabled then prepend the endpoint
if ($dualstackEnabled)

View file

@ -3,7 +3,7 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/

View file

@ -3,7 +3,7 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/