[s3_storage] Bump version of akeeba/s3 to version 2.3.1

- Address https://github.com/friendica/friendica/issues/12011#issuecomment-1854681792
This commit is contained in:
Hypolite Petovan 2023-12-18 21:28:16 -05:00
parent 9daa11eb10
commit 3e74af9775
61 changed files with 1472 additions and 708 deletions

View file

@ -3,29 +3,29 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Engine\Postproc\Connector\S3v4;
namespace Akeeba\S3;
// Protection against direct access
defined('AKEEBAENGINE') or die();
defined('AKEEBAENGINE') || die();
/**
* Shortcuts to often used access control privileges
*/
class Acl
{
const ACL_PRIVATE = 'private';
public const ACL_PRIVATE = 'private';
const ACL_PUBLIC_READ = 'public-read';
public const ACL_PUBLIC_READ = 'public-read';
const ACL_PUBLIC_READ_WRITE = 'public-read-write';
public const ACL_PUBLIC_READ_WRITE = 'public-read-write';
const ACL_AUTHENTICATED_READ = 'authenticated-read';
public const ACL_AUTHENTICATED_READ = 'authenticated-read';
const ACL_BUCKET_OWNER_READ = 'bucket-owner-read';
public const ACL_BUCKET_OWNER_READ = 'bucket-owner-read';
const ACL_BUCKET_OWNER_FULL_CONTROL = 'bucket-owner-full-control';
public const ACL_BUCKET_OWNER_FULL_CONTROL = 'bucket-owner-full-control';
}

View file

@ -3,14 +3,14 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Engine\Postproc\Connector\S3v4;
namespace Akeeba\S3;
// Protection against direct access
defined('AKEEBAENGINE') or die();
defined('AKEEBAENGINE') || die();
/**
* Holds the Amazon S3 confiugration credentials
@ -199,6 +199,8 @@ class Configuration
throw new Exception\InvalidSignatureMethod;
}
$this->signatureMethod = $signatureMethod;
// If you switch to v2 signatures we unset the region.
if ($signatureMethod == 'v2')
{
@ -214,13 +216,7 @@ class Configuration
$this->setUseLegacyPathStyle(false);
}
} else {
if (empty($this->getRegion())) {
$this->setRegion('us-east-1');
}
}
$this->signatureMethod = $signatureMethod;
}
/**

View file

@ -3,22 +3,22 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Engine\Postproc\Connector\S3v4;
namespace Akeeba\S3;
// Protection against direct access
use Akeeba\Engine\Postproc\Connector\S3v4\Exception\CannotDeleteFile;
use Akeeba\Engine\Postproc\Connector\S3v4\Exception\CannotGetBucket;
use Akeeba\Engine\Postproc\Connector\S3v4\Exception\CannotGetFile;
use Akeeba\Engine\Postproc\Connector\S3v4\Exception\CannotListBuckets;
use Akeeba\Engine\Postproc\Connector\S3v4\Exception\CannotOpenFileForWrite;
use Akeeba\Engine\Postproc\Connector\S3v4\Exception\CannotPutFile;
use Akeeba\Engine\Postproc\Connector\S3v4\Response\Error;
use Akeeba\S3\Exception\CannotDeleteFile;
use Akeeba\S3\Exception\CannotGetBucket;
use Akeeba\S3\Exception\CannotGetFile;
use Akeeba\S3\Exception\CannotListBuckets;
use Akeeba\S3\Exception\CannotOpenFileForWrite;
use Akeeba\S3\Exception\CannotPutFile;
use Akeeba\S3\Response\Error;
defined('AKEEBAENGINE') or die();
defined('AKEEBAENGINE') || die();
class Connector
{
@ -81,7 +81,10 @@ class Connector
if (($input->getSize() <= 0) || (($input->getInputType() == Input::INPUT_DATA) && (!strlen($input->getDataReference()))))
{
throw new CannotPutFile('Missing input parameters', 0);
if (substr($uri, -1) !== '/')
{
throw new CannotPutFile('Missing input parameters', 0);
}
}
// We need to post with Content-Length and Content-Type, MD5 is optional
@ -169,7 +172,7 @@ class Connector
if (!is_resource($saveTo) && is_string($saveTo))
{
$fp = @fopen($saveTo, 'wb');
$fp = @fopen($saveTo, 'w');
if ($fp === false)
{
@ -193,6 +196,53 @@ class Connector
$request->setHeader('Range', "bytes=$from-$to");
}
$response = $request->getResponse(true);
if (!$response->error->isError() && (($response->code !== 200) && ($response->code !== 206)))
{
$response->error = new Error(
$response->code,
"Unexpected HTTP status {$response->code}"
);
}
if ($response->error->isError())
{
throw new CannotGetFile(
sprintf(
__METHOD__ . "({%s}, {%s}): [%s] %s\n\nDebug info:\n%s",
$bucket,
$uri,
$response->error->getCode(),
$response->error->getMessage(),
print_r($response->body, true)
)
);
}
if (!is_resource($fp))
{
return $response->body;
}
return null;
}
/**
* Get information about an object.
*
* @param string $bucket Bucket name
* @param string $uri Object URI
*
* @return array The headers returned by Amazon S3
*
* @throws CannotGetFile If the file does not exist
* @see https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadObject.html
*/
public function headObject(string $bucket, string $uri): array
{
$request = new Request('HEAD', $bucket, $uri, $this->configuration);
$response = $request->getResponse();
if (!$response->error->isError() && (($response->code !== 200) && ($response->code !== 206)))
@ -206,20 +256,21 @@ class Connector
if ($response->error->isError())
{
throw new CannotGetFile(
sprintf(__METHOD__ . "({$bucket}, {$uri}): [%s] %s\n\nDebug info:\n%s",
$response->error->getCode(), $response->error->getMessage(), print_r($response->body, true)),
$response->error->getCode()
sprintf(
__METHOD__ . "({%s}, {%s}): [%s] %s\n\nDebug info:\n%s",
$bucket,
$uri,
$response->error->getCode(),
$response->error->getMessage(),
print_r($response->body, true)
)
);
}
if (!is_resource($fp))
{
return $response->body;
}
return null;
return $response->getHeaders();
}
/**
* Delete an object
*
@ -244,9 +295,13 @@ class Connector
if ($response->error->isError())
{
throw new CannotDeleteFile(
sprintf(__METHOD__ . "({$bucket}, {$uri}): [%s] %s",
$response->error->getCode(), $response->error->getMessage()),
$response->error->getCode()
sprintf(
__METHOD__ . "({%s}, {%s}): [%s] %s",
$bucket,
$uri,
$response->error->getCode(),
$response->error->getMessage()
)
);
}
}
@ -358,8 +413,7 @@ class Connector
if ($response->error->isError())
{
throw new CannotGetBucket(
sprintf(__METHOD__ . "(): [%s] %s", $response->error->getCode(), $response->error->getMessage()),
$response->error->getCode()
sprintf(__METHOD__ . "(): [%s] %s", $response->error->getCode(), $response->error->getMessage())
);
}
@ -403,168 +457,47 @@ class Connector
*/
public function getBucket(string $bucket, ?string $prefix = null, ?string $marker = null, ?int $maxKeys = null, string $delimiter = '/', bool $returnCommonPrefixes = false): array
{
$request = new Request('GET', $bucket, '', $this->configuration);
$internalResult = $this->internalGetBucket($bucket, $prefix, $marker, $maxKeys, $delimiter, $returnCommonPrefixes);
if (!empty($prefix))
{
$request->setParameter('prefix', $prefix);
}
/**
* @var array $objects
* @var ?string $nextMarker
*/
extract($internalResult);
unset($internalResult);
if (!empty($marker))
{
$request->setParameter('marker', $marker);
}
if (!empty($maxKeys))
{
$request->setParameter('max-keys', $maxKeys);
}
if (!empty($delimiter))
{
$request->setParameter('delimiter', $delimiter);
}
$response = $request->getResponse();
if (!$response->error->isError() && $response->code !== 200)
{
$response->error = new Error(
$response->code,
"Unexpected HTTP status {$response->code}"
);
}
if ($response->error->isError())
{
throw new CannotGetBucket(
sprintf(__METHOD__ . "(): [%s] %s", $response->error->getCode(), $response->error->getMessage()),
$response->error->getCode()
);
}
$results = [];
$nextMarker = null;
if ($response->hasBody() && isset($response->body->Contents))
{
foreach ($response->body->Contents as $c)
{
$results[(string) $c->Key] = [
'name' => (string) $c->Key,
'time' => strtotime((string) $c->LastModified),
'size' => (int) $c->Size,
'hash' => substr((string) $c->ETag, 1, -1),
];
$nextMarker = (string) $c->Key;
}
}
if ($returnCommonPrefixes && $response->hasBody() && isset($response->body->CommonPrefixes))
{
foreach ($response->body->CommonPrefixes as $c)
{
$results[(string) $c->Prefix] = ['prefix' => (string) $c->Prefix];
}
}
if ($response->hasBody() && isset($response->body->IsTruncated) &&
((string) $response->body->IsTruncated == 'false')
)
{
return $results;
}
if ($response->hasBody() && isset($response->body->NextMarker))
{
$nextMarker = (string) $response->body->NextMarker;
}
// Is it a truncated result?
$isTruncated = ($nextMarker !== null) && ((string) $response->body->IsTruncated == 'true');
// Is this a truncated result and no maxKeys specified?
$isTruncatedAndNoMaxKeys = ($maxKeys == null) && $isTruncated;
// Is this a truncated result with less keys than the specified maxKeys; and common prefixes found but not returned to the caller?
$isTruncatedAndNeedsContinue = ($maxKeys != null) && $isTruncated && (count($results) < $maxKeys);
// Loop through truncated results if maxKeys isn't specified
if ($isTruncatedAndNoMaxKeys || $isTruncatedAndNeedsContinue)
// Loop through truncated results if maxKeys isn't specified or we don't have enough object records yet.
if ($nextMarker !== null && ($maxKeys === null || count($objects) < $maxKeys))
{
do
{
$request = new Request('GET', $bucket, '', $this->configuration);
$internalResult = $this->internalGetBucket($bucket, $prefix, $nextMarker, $maxKeys, $delimiter, $returnCommonPrefixes);
if (!empty($prefix))
{
$request->setParameter('prefix', $prefix);
}
$nextMarker = $internalResult['nextMarker'];
$objects = array_merge($objects, $internalResult['objects']);
$request->setParameter('marker', $nextMarker);
unset($internalResult);
if (!empty($delimiter))
{
$request->setParameter('delimiter', $delimiter);
}
try
{
$response = $request->getResponse();
}
catch (\Exception $e)
// If the last call did not return a nextMarker I am done iterating.
if ($nextMarker === null)
{
break;
}
if ($response->hasBody() && isset($response->body->Contents))
// If we have maxKeys AND the number of objects is at least this many I am done iterating.
if ($maxKeys !== null && count($objects) >= $maxKeys)
{
foreach ($response->body->Contents as $c)
{
$results[(string) $c->Key] = [
'name' => (string) $c->Key,
'time' => strtotime((string) $c->LastModified),
'size' => (int) $c->Size,
'hash' => substr((string) $c->ETag, 1, -1),
];
$nextMarker = (string) $c->Key;
}
break;
}
if ($returnCommonPrefixes && $response->hasBody() && isset($response->body->CommonPrefixes))
{
foreach ($response->body->CommonPrefixes as $c)
{
$results[(string) $c->Prefix] = ['prefix' => (string) $c->Prefix];
}
}
if ($response->hasBody() && isset($response->body->NextMarker))
{
$nextMarker = (string) $response->body->NextMarker;
}
$continueCondition = false;
if ($isTruncatedAndNoMaxKeys)
{
$continueCondition = !$response->error->isError() && $isTruncated;
}
if ($isTruncatedAndNeedsContinue)
{
$continueCondition = !$response->error->isError() && $isTruncated && (count($results) < $maxKeys);
}
} while ($continueCondition);
} while (true);
}
if (!is_null($maxKeys))
if ($maxKeys !== null)
{
$results = array_splice($results, 0, $maxKeys);
return array_splice($objects, 0, $maxKeys);
}
return $results;
return $objects;
}
/**
@ -594,8 +527,7 @@ class Connector
if ($response->error->isError())
{
throw new CannotListBuckets(
sprintf(__METHOD__ . "(): [%s] %s", $response->error->getCode(), $response->error->getMessage()),
$response->error->getCode()
sprintf(__METHOD__ . "(): [%s] %s", $response->error->getCode(), $response->error->getMessage())
);
}
@ -691,7 +623,12 @@ class Connector
if ($response->error->isError())
{
throw new CannotPutFile(
sprintf(__METHOD__ . "(): [%s] %s\n\nDebug info:\n%s", $response->error->getCode(), $response->error->getMessage(), print_r($response->body, true))
sprintf(
__METHOD__ . "(): [%s] %s\n\nDebug info:\n%s",
$response->error->getCode(),
$response->error->getMessage(),
print_r($response->body, true)
)
);
}
@ -958,4 +895,90 @@ class Connector
{
return $this->configuration;
}
private function internalGetBucket(string $bucket, ?string $prefix = null, ?string $marker = null, ?int $maxKeys = null, string $delimiter = '/', bool $returnCommonPrefixes = false): array
{
$request = new Request('GET', $bucket, '', $this->configuration);
if (!empty($prefix))
{
$request->setParameter('prefix', $prefix);
}
if (!empty($marker))
{
$request->setParameter('marker', $marker);
}
if (!empty($maxKeys))
{
$request->setParameter('max-keys', $maxKeys);
}
if (!empty($delimiter))
{
$request->setParameter('delimiter', $delimiter);
}
$response = $request->getResponse();
if (!$response->error->isError() && $response->code !== 200)
{
$response->error = new Error(
$response->code,
"Unexpected HTTP status {$response->code}"
);
}
if ($response->error->isError())
{
throw new CannotGetBucket(
sprintf(__METHOD__ . "(): [%s] %s", $response->error->getCode(), $response->error->getMessage())
);
}
$results = [
'objects' => [],
'nextMarker' => null,
];
if ($response->hasBody() && isset($response->body->Contents))
{
foreach ($response->body->Contents as $c)
{
$results['objects'][(string) $c->Key] = [
'name' => (string) $c->Key,
'time' => strtotime((string) $c->LastModified),
'size' => (int) $c->Size,
'hash' => substr((string) $c->ETag, 1, -1),
];
$results['nextMarker'] = (string) $c->Key;
}
}
if ($returnCommonPrefixes && $response->hasBody() && isset($response->body->CommonPrefixes))
{
foreach ($response->body->CommonPrefixes as $c)
{
$results['objects'][(string) $c->Prefix] = ['prefix' => (string) $c->Prefix];
}
}
if ($response->hasBody() && isset($response->body->IsTruncated) &&
((string) $response->body->IsTruncated == 'false')
)
{
$results['nextMarker'] = null;
return $results;
}
if ($response->hasBody() && isset($response->body->NextMarker))
{
$results['nextMarker'] = (string) $response->body->NextMarker;
}
return $results;
}
}

View file

@ -3,14 +3,14 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
namespace Akeeba\S3\Exception;
// Protection against direct access
defined('AKEEBAENGINE') or die();
defined('AKEEBAENGINE') || die();
use RuntimeException;

View file

@ -3,14 +3,14 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
namespace Akeeba\S3\Exception;
// Protection against direct access
defined('AKEEBAENGINE') or die();
defined('AKEEBAENGINE') || die();
use RuntimeException;

View file

@ -3,14 +3,14 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
namespace Akeeba\S3\Exception;
// Protection against direct access
defined('AKEEBAENGINE') or die();
defined('AKEEBAENGINE') || die();
use RuntimeException;

View file

@ -3,14 +3,14 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
namespace Akeeba\S3\Exception;
// Protection against direct access
defined('AKEEBAENGINE') or die();
defined('AKEEBAENGINE') || die();
use RuntimeException;

View file

@ -3,14 +3,14 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
namespace Akeeba\S3\Exception;
// Protection against direct access
defined('AKEEBAENGINE') or die();
defined('AKEEBAENGINE') || die();
use Exception;
use RuntimeException;

View file

@ -3,14 +3,14 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
namespace Akeeba\S3\Exception;
// Protection against direct access
defined('AKEEBAENGINE') or die();
defined('AKEEBAENGINE') || die();
use Exception;
use RuntimeException;

View file

@ -3,14 +3,14 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
namespace Akeeba\S3\Exception;
// Protection against direct access
defined('AKEEBAENGINE') or die();
defined('AKEEBAENGINE') || die();
use RuntimeException;

View file

@ -3,14 +3,14 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
namespace Akeeba\S3\Exception;
// Protection against direct access
defined('AKEEBAENGINE') or die();
defined('AKEEBAENGINE') || die();
use RuntimeException;

View file

@ -3,14 +3,14 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
namespace Akeeba\S3\Exception;
// Protection against direct access
defined('AKEEBAENGINE') or die();
defined('AKEEBAENGINE') || die();
use Exception;

View file

@ -3,14 +3,14 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
namespace Akeeba\S3\Exception;
// Protection against direct access
defined('AKEEBAENGINE') or die();
defined('AKEEBAENGINE') || die();
use Exception;
use RuntimeException;

View file

@ -3,14 +3,14 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
namespace Akeeba\S3\Exception;
// Protection against direct access
defined('AKEEBAENGINE') or die();
defined('AKEEBAENGINE') || die();
use Exception;

View file

@ -3,14 +3,14 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
namespace Akeeba\S3\Exception;
// Protection against direct access
defined('AKEEBAENGINE') or die();
defined('AKEEBAENGINE') || die();
use Exception;
use InvalidArgumentException;

View file

@ -3,14 +3,14 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
namespace Akeeba\S3\Exception;
// Protection against direct access
defined('AKEEBAENGINE') or die();
defined('AKEEBAENGINE') || die();
use Exception;

View file

@ -3,14 +3,14 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
namespace Akeeba\S3\Exception;
// Protection against direct access
defined('AKEEBAENGINE') or die();
defined('AKEEBAENGINE') || die();
use Exception;

View file

@ -3,14 +3,14 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
namespace Akeeba\S3\Exception;
// Protection against direct access
defined('AKEEBAENGINE') or die();
defined('AKEEBAENGINE') || die();
use Exception;

View file

@ -3,14 +3,14 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
namespace Akeeba\S3\Exception;
// Protection against direct access
defined('AKEEBAENGINE') or die();
defined('AKEEBAENGINE') || die();
use LogicException;

View file

@ -3,14 +3,14 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Engine\Postproc\Connector\S3v4;
namespace Akeeba\S3;
// Protection against direct access
defined('AKEEBAENGINE') or die();
defined('AKEEBAENGINE') || die();
/**
* Defines an input source for PUT/POST requests to Amazon S3
@ -20,17 +20,17 @@ class Input
/**
* Input type: resource
*/
const INPUT_RESOURCE = 1;
public const INPUT_RESOURCE = 1;
/**
* Input type: file
*/
const INPUT_FILE = 2;
public const INPUT_FILE = 2;
/**
* Input type: raw data
*/
const INPUT_DATA = 3;
public const INPUT_DATA = 3;
/**
* File pointer, in case we have a resource
@ -177,7 +177,13 @@ class Input
{
if (is_resource($this->fp))
{
@fclose($this->fp);
try
{
@fclose($this->fp);
}
catch (\Throwable $e)
{
}
}
}
@ -258,10 +264,16 @@ class Input
if (is_resource($this->fp))
{
@fclose($this->fp);
try
{
@fclose($this->fp);
}
catch (\Throwable $e)
{
}
}
$this->fp = @fopen($file, 'rb');
$this->fp = @fopen($file, 'r');
if ($this->fp === false)
{
@ -295,7 +307,13 @@ class Input
if (is_resource($this->fp))
{
@fclose($this->fp);
try
{
@fclose($this->fp);
}
catch (\Throwable $e)
{
}
}
$this->file = null;
@ -329,7 +347,13 @@ class Input
if (is_resource($this->fp))
{
@fclose($this->fp);
try
{
@fclose($this->fp);
}
catch (\Throwable $e)
{
}
}
$this->file = null;
@ -450,7 +474,7 @@ class Input
*/
public function setSha256(?string $sha256): void
{
$this->sha256 = strtolower($sha256);
$this->sha256 = is_null($sha256) ? null : strtolower($sha256);
}
/**
@ -532,7 +556,7 @@ class Input
switch ($this->getInputType())
{
case self::INPUT_DATA:
return function_exists('mb_strlen') ? mb_strlen($this->data, '8bit') : strlen($this->data);
return function_exists('mb_strlen') ? mb_strlen($this->data ?? '', '8bit') : strlen($this->data ?? '');
break;
case self::INPUT_FILE:
@ -635,7 +659,7 @@ class Input
$ext = strtolower(pathInfo($file, PATHINFO_EXTENSION));
return isset($exts[$ext]) ? $exts[$ext] : 'application/octet-stream';
return $exts[$ext] ?? 'application/octet-stream';
}
/**

View file

@ -3,16 +3,16 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Engine\Postproc\Connector\S3v4;
namespace Akeeba\S3;
use Akeeba\Engine\Postproc\Connector\S3v4\Response\Error;
use Akeeba\S3\Response\Error;
// Protection against direct access
defined('AKEEBAENGINE') or die();
defined('AKEEBAENGINE') || die();
class Request
@ -142,6 +142,12 @@ class Request
// The date must always be added as a header
$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)
{
$this->headers['Date'] = gmdate('D, d M Y H:i:s T');
}
// If there is a security token we need to set up the X-Amz-Security-Token header
$token = $this->configuration->getToken();
@ -367,7 +373,7 @@ class Request
*
* @return Response
*/
public function getResponse(): Response
public function getResponse(bool $rawResponse = false): Response
{
$this->processParametersIntoResource();
@ -417,8 +423,10 @@ class Request
* Caveat: if your bucket contains dots in the name we have to turn off host verification due to the way the
* S3 SSL certificates are set up.
*/
$isAmazonS3 = (substr($this->headers['Host'], -14) == '.amazonaws.com') ||
substr($this->headers['Host'], -16) == 'amazonaws.com.cn';
$isAmazonS3 = (substr($this->headers['Host'], -14) == '.amazonaws.com')
|| substr(
$this->headers['Host'], -16
) == 'amazonaws.com.cn';
$tooManyDots = substr_count($this->headers['Host'], '.') > 4;
$verifyHost = ($isAmazonS3 && $tooManyDots) ? 0 : 2;
@ -429,6 +437,27 @@ class Request
curl_setopt($curl, CURLOPT_URL, $url);
/**
* Set the optional x-amz-date header for third party services.
*
* 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.
*/
$this->headers['x-amz-date'] = strpos($this->headers['Host'], '.amazonaws.com') !== false
? ''
: (new \DateTime($this->headers['Date']))->format('Ymd\THis\Z');
/**
* Remove empty headers.
*
* While Amazon S3 proper and most third party implementations have no problem with that, there a few of them
* (such as Synology C2) which choke on empty headers.
*/
$this->headers = array_filter($this->headers);
// Get the request signature
$signer = Signature::getSignatureObject($this, $this->configuration->getSignatureMethod());
$signer->preProcessHeaders($this->headers, $this->amzHeaders);
@ -482,7 +511,7 @@ class Request
$data = $this->input->getDataReference();
if (strlen($data))
if (strlen($data ?? ''))
{
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
}
@ -538,12 +567,18 @@ class Request
@curl_close($curl);
// Set the body data
$this->response->finaliseBody();
$this->response->finaliseBody($rawResponse);
// Clean up file resources
if (!is_null($this->fp) && is_resource($this->fp))
{
fclose($this->fp);
try
{
@fclose($this->fp);
}
catch (\Throwable $e)
{
}
}
return $this->response;
@ -560,7 +595,7 @@ class Request
*/
protected function __responseWriteCallback($curl, string $data): int
{
if (in_array($this->response->code, [200, 206]) && !is_null($this->fp) && is_resource($this->fp))
if (in_array($this->response->code, [0, 200, 206]) && !is_null($this->fp) && is_resource($this->fp))
{
return fwrite($this->fp, $data);
}
@ -573,7 +608,7 @@ class Request
/**
* cURL header callback
*
* @param resource $curl cURL resource
* @param resource $curl cURL resource
* @param string &$data Data
*
* @return int Length in bytes
@ -592,7 +627,15 @@ class Request
return $strlen;
}
[$header, $value] = explode(': ', trim($data), 2);
// Ignore malformed headers without a value.
if (strpos($data, ':') === false)
{
return $strlen;
}
[$header, $value] = explode(':', trim($data), 2);
$header = trim($header ?? '');
$value = trim($value ?? '');
switch (strtolower($header))
{
@ -609,10 +652,12 @@ class Request
break;
case 'etag':
$this->response->setHeader('hash', $value[0] == '"' ? substr($value, 1, -1) : $value);
$this->response->setHeader('hash', trim($value, '"'));
break;
default:
$this->response->setHeader(strtolower($header), is_numeric($value) ? (int) $value : $value);
if (preg_match('/^x-amz-meta-.*$/', $header))
{
$this->setHeader($header, is_numeric($value) ? (int) $value : $value);
@ -652,13 +697,12 @@ class Request
$query = substr($query, 0, -1);
$this->uri .= $query;
if (array_key_exists('acl', $this->parameters) ||
array_key_exists('location', $this->parameters) ||
array_key_exists('torrent', $this->parameters) ||
array_key_exists('logging', $this->parameters) ||
array_key_exists('uploads', $this->parameters) ||
array_key_exists('uploadId', $this->parameters) ||
array_key_exists('partNumber', $this->parameters)
if (array_key_exists('acl', $this->parameters) || array_key_exists('location', $this->parameters)
|| array_key_exists('torrent', $this->parameters)
|| array_key_exists('logging', $this->parameters)
|| array_key_exists('uploads', $this->parameters)
|| array_key_exists('uploadId', $this->parameters)
|| array_key_exists('partNumber', $this->parameters)
)
{
$this->resource .= $query;
@ -720,6 +764,8 @@ class Request
}
/**
* Only applies to Amazon S3 proper.
*
* When using the Amazon S3 with the v4 signature API we have to use a different hostname per region. The
* mapping can be found in https://docs.aws.amazon.com/general/latest/gr/s3.html#s3_region
*
@ -728,25 +774,27 @@ class Request
*
* v4 signing does NOT support non-Amazon endpoints.
*/
// Most endpoints: s3-REGION.amazonaws.com
$regionalEndpoint = $region . '.amazonaws.com';
// Exception: China
if (substr($region, 0, 3) == 'cn-')
if (in_array($endpoint, ['s3.amazonaws.com', 'amazonaws.com.cn']))
{
// Chinese endpoint, e.g.: s3.cn-north-1.amazonaws.com.cn
$regionalEndpoint = $regionalEndpoint . '.cn';
}
// Most endpoints: s3-REGION.amazonaws.com
$regionalEndpoint = $region . '.amazonaws.com';
// If dual-stack URLs are enabled then prepend the endpoint
if ($configuration->getDualstackUrl())
{
$endpoint = 's3.dualstack.' . $regionalEndpoint;
}
else
{
$endpoint = 's3.' . $regionalEndpoint;
// Exception: China
if (substr($region, 0, 3) == 'cn-')
{
// Chinese endpoint, e.g.: s3.cn-north-1.amazonaws.com.cn
$regionalEndpoint = $regionalEndpoint . '.cn';
}
// If dual-stack URLs are enabled then prepend the endpoint
if ($configuration->getDualstackUrl())
{
$endpoint = 's3.dualstack.' . $regionalEndpoint;
}
else
{
$endpoint = 's3.' . $regionalEndpoint;
}
}
// Legacy path style access: return just the endpoint

View file

@ -3,18 +3,18 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Engine\Postproc\Connector\S3v4;
namespace Akeeba\S3;
use Akeeba\Engine\Postproc\Connector\S3v4\Exception\PropertyNotFound;
use Akeeba\Engine\Postproc\Connector\S3v4\Response\Error;
use Akeeba\S3\Exception\PropertyNotFound;
use Akeeba\S3\Response\Error;
use SimpleXMLElement;
// Protection against direct access
defined('AKEEBAENGINE') or die();
defined('AKEEBAENGINE') || die();
/**
* Amazon S3 API response object
@ -124,7 +124,7 @@ class Response
*
* @param string|SimpleXMLElement|null $body
*/
public function setBody($body): void
public function setBody($body, bool $rawResponse = false): void
{
$this->body = null;
@ -135,7 +135,7 @@ class Response
$this->body = $body;
$this->finaliseBody();
$this->finaliseBody($rawResponse);
}
public function resetBody(): void
@ -153,7 +153,7 @@ class Response
$this->body .= $data;
}
public function finaliseBody(): void
public function finaliseBody(bool $rawResponse = false): void
{
if (!$this->hasBody())
{
@ -165,8 +165,14 @@ class Response
$this->headers['type'] = 'text/plain';
}
if (is_string($this->body) &&
(($this->headers['type'] == 'application/xml') || (substr($this->body, 0, 5) == '<?xml'))
if (
!$rawResponse
&& is_string($this->body)
&&
(
($this->headers['type'] == 'application/xml')
|| (substr($this->body, 0, 5) == '<?xml')
)
)
{
$this->body = simplexml_load_string($this->body);
@ -332,8 +338,8 @@ class Response
)
{
$this->error = new Error(
$this->code,
(string) $this->body->Message
500,
(string) $this->body->Code . ':' . (string) $this->body->Message
);
if (isset($this->body->Resource))

View file

@ -3,14 +3,14 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Engine\Postproc\Connector\S3v4\Response;
namespace Akeeba\S3\Response;
// Protection against direct access
defined('AKEEBAENGINE') or die();
defined('AKEEBAENGINE') || die();
/**
* S3 response error object

View file

@ -3,14 +3,14 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Engine\Postproc\Connector\S3v4;
namespace Akeeba\S3;
// Protection against direct access
defined('AKEEBAENGINE') or die();
defined('AKEEBAENGINE') || die();
/**
* Base class for request signing objects.
@ -44,7 +44,7 @@ abstract class Signature
*/
public static function getSignatureObject(Request $request, string $method = 'v2'): self
{
$className = '\\Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Signature\\' . ucfirst($method);
$className = __NAMESPACE__ . '\\Signature\\' . ucfirst($method);
return new $className($request);
}

View file

@ -3,16 +3,16 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Engine\Postproc\Connector\S3v4\Signature;
namespace Akeeba\S3\Signature;
// Protection against direct access
defined('AKEEBAENGINE') or die();
defined('AKEEBAENGINE') || die();
use Akeeba\Engine\Postproc\Connector\S3v4\Signature;
use Akeeba\S3\Signature;
/**
* Implements the Amazon AWS v2 signatures
@ -123,7 +123,7 @@ class V2 extends Signature
}
// AMZ headers must be sorted and sent as separate lines
if (sizeof($amz) > 0)
if (count($amz) > 0)
{
sort($amz);
$amzString = "\n" . implode("\n", $amz);
@ -150,8 +150,8 @@ class V2 extends Signature
}
$stringToSign = $verb . "\n" .
(isset($headers['Content-MD5']) ? $headers['Content-MD5'] : '') . "\n" .
(isset($headers['Content-Type']) ? $headers['Content-Type'] : '') . "\n" .
($headers['Content-MD5'] ?? '') . "\n" .
($headers['Content-Type'] ?? '') . "\n" .
$headers['Date'] .
$amzString . "\n" .
$resourcePath;

View file

@ -3,16 +3,16 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Engine\Postproc\Connector\S3v4\Signature;
namespace Akeeba\S3\Signature;
// Protection against direct access
defined('AKEEBAENGINE') or die();
defined('AKEEBAENGINE') || die();
use Akeeba\Engine\Postproc\Connector\S3v4\Signature;
use Akeeba\S3\Signature;
use DateTime;
/**
@ -77,14 +77,20 @@ class V4 extends Signature
* http://s3-eu-west-1.amazonaws.com/example instead of http://example.amazonaws.com/ for all authenticated URLs
*/
$region = $this->request->getConfiguration()->getRegion();
$bucket = $this->request->getBucket();
$hostname = $this->getPresignedHostnameForRegion($region);
if ($this->isValidBucketName($bucket))
{
$hostname = $bucket . '.' . $hostname;
}
$this->request->setHeader('Host', $hostname);
// Set the expiration time in seconds
$this->request->setHeader('Expires', (int) $lifetime);
// Get the query parameters, including the calculated signature
$bucket = $this->request->getBucket();
$uri = $this->request->getResource();
$headers = $this->request->getHeaders();
$protocol = $https ? 'https' : 'http';
@ -93,6 +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)
{
$uri = substr($uri, strlen($bucket) + 1);
}
$query = http_build_query($queryParameters);
$url = $protocol . '://' . $headers['Host'] . $uri;
@ -129,8 +140,8 @@ class V4 extends Signature
$signatureDate = new DateTime($headers['Date']);
$credentialScope = $signatureDate->format('Ymd') . '/' .
$this->request->getConfiguration()->getRegion() . '/' .
's3/aws4_request';
$this->request->getConfiguration()->getRegion() . '/' .
's3/aws4_request';
/**
* If the Expires header is set up we're pre-signing a download URL. The string to sign is a bit
@ -208,12 +219,14 @@ class V4 extends Signature
// The canonical URI is the resource path
$canonicalURI = $resourcePath;
$bucketResource = '/' . $bucket;
$regionalHostname = ($headers['Host'] != 's3.amazonaws.com') && ($headers['Host'] != $bucket . '.s3.amazonaws.com');
$regionalHostname = ($headers['Host'] != 's3.amazonaws.com')
&& ($headers['Host'] != $bucket . '.s3.amazonaws.com');
// 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!!!
if (!$regionalHostname && ($headers['Host'] == 's3.amazonaws.com') && (substr($canonicalURI, -10) == '/?location'))
if (!$regionalHostname && ($headers['Host'] == 's3.amazonaws.com')
&& (substr($canonicalURI, -10) == '/?location'))
{
$regionalHostname = true;
}
@ -274,11 +287,11 @@ class V4 extends Signature
// Calculate the canonical request
$canonicalRequest = $verb . "\n" .
$canonicalURI . "\n" .
$canonicalQueryString . "\n" .
$canonicalHeaders . "\n" .
$signedHeaders . "\n" .
$requestPayloadHash;
$canonicalURI . "\n" .
$canonicalQueryString . "\n" .
$canonicalHeaders . "\n" .
$signedHeaders . "\n" .
$requestPayloadHash;
$hashedCanonicalRequest = hash('sha256', $canonicalRequest);
@ -290,17 +303,40 @@ class V4 extends Signature
$headers['Date'] = '';
}
/**
* The Date in the String-to-Sign is a messy situation.
*
* Amazon's documentation says it must be in ISO 8601 format: `Ymd\THis\Z`. Unfortunately, Amazon's
* documentation is actually wrong :troll_face: The actual Amazon S3 service expects the date to be formatted as
* per RFC1123.
*
* Most third party implementations have caught up to the fact that Amazon has documented the v4 signatures
* wrongly (naughty AWS!) and accept either format.
*
* Some other third party implementations, which never bothered to validate their implementations against Amazon
* S3 proper, only expect what Amazon has documented as "ISO 8601". Therefore, we detect third party services
* and switch to the as-documented date format.
*
* Some other third party services, like Wasabi, are broken in yet a different way. They will only use the date
* from the x-amz-date header, WITHOUT falling back to the Date header if the former is not present. This is
* the opposite of Amazon S3 proper which does expect the Date header. That's why the Request class sets both
* headers if the request is made to a service _other_ than Amazon S3 proper.
*/
$dateToSignFor = strpos($headers['Host'], '.amazonaws.com') !== false
? $headers['Date']
: $signatureDate->format('Ymd\THis\Z');
$stringToSign = "AWS4-HMAC-SHA256\n" .
$headers['Date'] . "\n" .
$credentialScope . "\n" .
$hashedCanonicalRequest;
$dateToSignFor . "\n" .
$credentialScope . "\n" .
$hashedCanonicalRequest;
if ($isPresignedURL)
{
$stringToSign = "AWS4-HMAC-SHA256\n" .
$parameters['X-Amz-Date'] . "\n" .
$credentialScope . "\n" .
$hashedCanonicalRequest;
$parameters['X-Amz-Date'] . "\n" .
$credentialScope . "\n" .
$hashedCanonicalRequest;
}
// ========== Step 3: Calculate the signature ==========
@ -313,9 +349,9 @@ class V4 extends Signature
// See http://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html
$authorization = 'AWS4-HMAC-SHA256 Credential=' .
$this->request->getConfiguration()->getAccess() . '/' . $credentialScope . ', ' .
'SignedHeaders=' . $signedHeaders . ', ' .
'Signature=' . $signature;
$this->request->getConfiguration()->getAccess() . '/' . $credentialScope . ', ' .
'SignedHeaders=' . $signedHeaders . ', ' .
'Signature=' . $signature;
// For presigned URLs we only return the Base64-encoded signature without the AWS format specifier and the
// public access key.
@ -366,7 +402,14 @@ class V4 extends Signature
*/
private function getPresignedHostnameForRegion(string $region): string
{
$endpoint = 's3.' . $region . '.amazonaws.com';
$config = $this->request->getConfiguration();
$endpoint = $config->getEndpoint();
if (empty($endpoint))
{
$endpoint = 's3.' . $region . '.amazonaws.com';
}
$dualstackEnabled = $this->request->getConfiguration()->getDualstackUrl();
// If dual-stack URLs are enabled then prepend the endpoint
@ -382,4 +425,83 @@ class V4 extends Signature
return $endpoint;
}
/**
* Is this a valid bucket name?
*
* @param string $bucketName The bucket name to check
* @param bool $asSubdomain Should I put additional restrictions for use as a subdomain?
*
* @return bool
* @since 2.3.1
*
* @see https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html
*/
private function isValidBucketName(string $bucketName, bool $asSubdomain = true): bool
{
/**
* If there are dots in the bucket name I can't use it as a subdomain.
*
* "If you include dots in a bucket's name, you can't use virtual-host-style addressing over HTTPS, unless you
* perform your own certificate validation. This is because the security certificates used for virtual hosting
* of buckets don't work for buckets with dots in their names."
*/
if ($asSubdomain && strpos($bucketName, '.') !== false)
{
return false;
}
/**
* - Bucket names must be between 3 (min) and 63 (max) characters long.
* - Bucket names can consist only of lowercase letters, numbers, dots (.), and hyphens (-).
*/
if (!preg_match('/^[a-z0-9\-.]{3,63}$/', $bucketName))
{
return false;
}
// Bucket names must begin and end with a letter or number.
if (!preg_match('/^[a-z0-9].*[a-z0-9]$/', $bucketName))
{
return false;
}
// Bucket names must not contain two adjacent periods.
if (preg_match('/\.\./', $bucketName))
{
return false;
}
// Bucket names must not be formatted as an IP address (for example, 192.168.5.4).
if (preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/', $bucketName))
{
return false;
}
// Bucket names must not start with the prefix xn--.
if (strpos($bucketName, 'xn--') === 0)
{
return false;
}
// Bucket names must not start with the prefix sthree- and the prefix sthree-configurator.
if (strpos($bucketName, 'sthree-') === 0)
{
return false;
}
// Bucket names must not end with the suffix -s3alias.
if (substr($bucketName, -8) === '-s3alias')
{
return false;
}
// Bucket names must not end with the suffix --ol-s3.
if (substr($bucketName, -7) === '--ol-s3')
{
return false;
}
return true;
}
}

View file

@ -3,11 +3,11 @@
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Engine\Postproc\Connector\S3v4;
namespace Akeeba\S3;
/**
* Amazon S3 Storage Classes
@ -28,39 +28,39 @@ class StorageClass
/**
* Amazon S3 Standard (S3 Standard)
*/
const STANDARD = 'STANDARD';
public const STANDARD = 'STANDARD';
/**
* Reduced redundancy storage
*
* Not recommended anymore. Use INTELLIGENT_TIERING instead.
*/
const REDUCED_REDUNDANCY = 'REDUCED_REDUNDANCY';
public const REDUCED_REDUNDANCY = 'REDUCED_REDUNDANCY';
/**
* Amazon S3 Intelligent-Tiering (S3 Intelligent-Tiering)
*/
const INTELLIGENT_TIERING = 'INTELLIGENT_TIERING';
public const INTELLIGENT_TIERING = 'INTELLIGENT_TIERING';
/**
* Amazon S3 Standard-Infrequent Access (S3 Standard-IA)
*/
const STANDARD_IA = 'STANDARD_IA';
public const STANDARD_IA = 'STANDARD_IA';
/**
* Amazon S3 One Zone-Infrequent Access (S3 One Zone-IA)
*/
const ONEZONE_IA = 'ONEZONE_IA';
public const ONEZONE_IA = 'ONEZONE_IA';
/**
* Amazon S3 Glacier (S3 Glacier)
*/
const GLACIER = 'GLACIER';
public const GLACIER = 'GLACIER';
/**
* Amazon S3 Glacier Deep Archive (S3 Glacier Deep Archive)
*/
const DEEP_ARCHIVE = 'DEEP_ARCHIVE';
public const DEEP_ARCHIVE = 'DEEP_ARCHIVE';
/**
* Manipulate the $headers array, setting the X-Amz-Storage-Class header for the requested storage class.

View file

@ -0,0 +1,35 @@
<?php
/**
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
/**
* Automatic aliasing of the old namespace to the new, as you use each old class.
*/
spl_autoload_register(
static function (string $className)
{
$oldNS = 'Akeeba\Engine\Postproc\Connector\S3v4';
$newNS = 'Akeeba\S3';
$className = trim($className, '\\');
if (strpos($className, $oldNS) !== 0)
{
return false;
}
$newClassName = $newNS . '\\' . trim(substr($className, strlen($oldNS)), '\\');
if (class_exists($newClassName, true))
{
class_alias($newClassName, $className, false);
}
return true;
}
);