"packages": [
{
"name": "akeeba/s3",
- "version": "2.0.0",
+ "version": "2.3.1",
"source": {
"type": "git",
"url": "https://github.com/akeeba/s3.git",
- "reference": "01520dae1f736555e08efda0ddc1044701bd340a"
+ "reference": "7f5b3e929c93eb02ba24472560c0cbbef735aed9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/akeeba/s3/zipball/01520dae1f736555e08efda0ddc1044701bd340a",
- "reference": "01520dae1f736555e08efda0ddc1044701bd340a",
+ "url": "https://api.github.com/repos/akeeba/s3/zipball/7f5b3e929c93eb02ba24472560c0cbbef735aed9",
+ "reference": "7f5b3e929c93eb02ba24472560c0cbbef735aed9",
"shasum": ""
},
"require": {
"ext-curl": "*",
"ext-simplexml": "*",
- "php": ">=7.1.0 <8.1"
+ "php": ">=7.1.0 <8.4"
},
"type": "library",
"autoload": {
+ "files": [
+ "src/aliasing.php"
+ ],
"psr-4": {
- "Akeeba\\Engine\\Postproc\\Connector\\S3v4\\": "src"
+ "Akeeba\\S3\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "GPL-3.0+"
+ "GPL-3.0-or-later"
],
"authors": [
{
"keywords": [
"s3"
],
- "time": "2020-11-30T14:03:55+00:00"
+ "time": "2023-09-26T11:40:10+00:00"
}
],
"packages-dev": [],
/.idea/
/000/
-/minitest/config.php
+/minitest/config.*
+!/minitest/config.dist.php
/minitest/tmp
/vendor/
## Why reinvent the wheel
-After having a lot of impossible to debug problems with Amazon's Guzzle-based AWS SDK we decided to roll our own connector for Amazon S3. This is by no means a complete implementation, just a small subset of S3's features which are required by our software. The design goals are simplicity, no external dependencies and low memory footprint.
+After having a lot of impossible to debug problems with Amazon's Guzzle-based AWS SDK we decided to roll our own connector for Amazon S3. This is by no means a complete implementation, just a small subset of S3's features which are required by our software. The design goals are simplicity, no external dependencies and a low memory footprint.
This code was originally based on [S3.php written by Donovan Schonknecht](http://undesigned.org.za/2007/10/22/amazon-s3-php-class) which is available under a BSD-like license. This repository no longer reflects the original author's work and should not be confused with it.
-This software is distributed under the GNU General Public License version 3 or, at your option, any later version published by the Free Software Foundation (FSF). In short, it's "GPLv3+".
+This software is distributed under the GNU General Public License version 3 or, at your option, any later version published by the Free Software Foundation (FSF). In short, it's GPL-3.0-or-later, as noted in composer.json.
-## Important note about version 2
+## Important notes about version 2
-Akeeba Amazon S3 Connector version 2 has dropped support for PPH 5.3 to 7.0 inclusive. It is only compatible with PHP 7.1 or later, up to and including PHP 8.0.
+### PHP version support since 2.0
-The most significant change in this version is that all methods use scalar type hints for parameters and return values. This _may_ break existing consumers which relied on implicit type conversion e.g. passing strings containing integer values instead of _actual_ integer values.
+Akeeba Amazon S3 Connector version 2 has dropped support for PHP 5.3 to 7.0 inclusive.
+
+The most significant change in this version is that all methods use scalar type hints for parameters and return values. This _may_ break existing consumers which relied on implicit type conversion.
+
+### Namespace change since 2.3
+
+Up to and including version 2.2 of the library, the namespace was `\Akeeba\Engine\Postproc\Connector\S3v4`. From version 2.3 of the library the namespace has changed to `\Akeeba\S3`.
+
+The library automatically registers aliases of the old classes to the new ones, thus ensuring updating the library will not introduce backwards incompatible changes. This is why it's not a major version update. Aliases will remain in place until at least version 3.0 of the library.
## Using the connector
+You need to define a constant before using or referencing any class in the library:
+
+```php
+defined('AKEEBAENGINE') or define('AKEEBAENGINE', 1);
+```
+
+All library files have a line similar to
+
+```php
+defined('AKEEBAENGINE') or die();
+```
+
+to prevent direct access to the libraries files. This is intentional. The primary use case for this library is mass-distributed software which gets installed in a publicly accessible subdirectory of the web root. This line prevents any accidental path disclosure from PHP error messages if someone were to access these files directly on misconfigured servers.
+
+If you are writing a Joomla extension, especially a plugin or module, please _always_ check if the constant has already been defined before defining it yourself. Thank you!
+
### Get a connector object
```php
-$configuration = new \Akeeba\Engine\Postproc\Connector\S3v4\Configuration(
+$configuration = new \Akeeba\S3\Configuration(
'YourAmazonAccessKey',
'YourAmazonSecretKey'
);
-$connector = new \Akeeba\Engine\Postproc\Connector\S3v4\Connector($configuration);
+$connector = new \Akeeba\S3\Connector($configuration);
```
If you are running inside an Amazon EC2 instance you can fetch temporary credentials from the instance's metadata
$role = file_get_contents('http://169.254.169.254/latest/meta-data/iam/security-credentials/');
$jsonCredentials = file_get_contents('http://169.254.169.254/latest/meta-data/iam/security-credentials/' . $role);
$credentials = json_decode($jsonCredentials, true);
-$configuration = new \Akeeba\Engine\Postproc\Connector\S3v4\Configuration(
+$configuration = new \Akeeba\S3\Configuration(
$credentials['AccessKeyId'],
$credentials['SecretAccessKey'],
'v4',
);
$configuration->setToken($credentials['Token']);
-$connector = new \Akeeba\Engine\Postproc\Connector\S3v4\Connector($configuration);
+$connector = new \Akeeba\S3\Connector($configuration);
```
where `$yourRegion` is the AWS region of your bucket, e.g. `us-east-1`. Please note that we are passing the security
token (`$credentials['Token']`) to the Configuration object. This is REQUIRED. The temporary credentials returned by
the metadata service won't work without it.
-Also worth noting is that the temporary credentials don't last forever. Check the `$credentials['Expiration']` to see
+Another point worth noting is that the temporary credentials don't last forever. Check the `$credentials['Expiration']` to see
when they are about to expire. Amazon recommends that you retry fetching new credentials from the metadata service
10 minutes before your cached credentials are set to expire. The metadata service is guaranteed to provision fresh
temporary credentials by that time.
From a file:
```php
-$input = \Akeeba\Engine\Postproc\Connector\S3v4\Input::createFromFile($sourceFile);
+$input = \Akeeba\S3\Input::createFromFile($sourceFile);
$connector->putObject($input, 'mybucket', 'path/to/myfile.txt');
```
From a string:
```php
-$input = \Akeeba\Engine\Postproc\Connector\S3v4\Input::createFromData($sourceString);
+$input = \Akeeba\S3\Input::createFromData($sourceString);
$connector->putObject($input, 'mybucket', 'path/to/myfile.txt');
```
From a stream resource:
```php
-$input = \Akeeba\Engine\Postproc\Connector\S3v4\Input::createFromResource($streamHandle, false);
+$input = \Akeeba\S3\Input::createFromResource($streamHandle, false);
$connector->putObject($input, 'mybucket', 'path/to/myfile.txt');
```
Files are uploaded in 5Mb chunks.
```php
-$input = \Akeeba\Engine\Postproc\Connector\S3v4\Input::createFromFile($sourceFile);
+$input = \Akeeba\S3\Input::createFromFile($sourceFile);
$uploadId = $connector->startMultipart($input, 'mybucket', 'mypath/movie.mov');
$eTags = array();
do
{
// IMPORTANT: You MUST create the input afresh before each uploadMultipart call
- $input = \Akeeba\Engine\Postproc\Connector\S3v4\Input::createFromFile($sourceFile);
+ $input = \Akeeba\S3\Input::createFromFile($sourceFile);
$input->setUploadID($uploadId);
$input->setPartNumber(++$partNumber);
while (!is_null($eTag));
// IMPORTANT: You MUST create the input afresh before finalising the multipart upload
-$input = \Akeeba\Engine\Postproc\Connector\S3v4\Input::createFromFile($sourceFile);
+$input = \Akeeba\S3\Input::createFromFile($sourceFile);
$input->setUploadID($uploadId);
$input->setEtags($eTags);
$connector->deleteObject('mybucket', 'path/to/file.jpg');
```
+### Test if an object exists
+
+```php
+try
+{
+ $headers = $connector->headObject('mybucket', 'path/to/file.jpg');
+ $exists = true;
+}
+catch (\Akeeba\S3\Exception\CannotGetFile $e)
+{
+ $headers = [];
+ $exists = false;
+}
+```
+
+The `$headers` variable contains an array with the S3 headers returned by the [HeadObject(https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadObject.html) API call. The header keys are always in lowercase. Please note that _not all_ of the headers Amazon describes in their documentation are returned in every request.
+
## Configuration options
The Configuration option has optional methods which can be used to enable some useful features in the connector.
You need to execute these methods against the Configuration object before passing it to the Connector's constructor. For example:
```php
-$configuration = new \Akeeba\Engine\Postproc\Connector\S3v4\Configuration(
+$configuration = new \Akeeba\S3\Configuration(
'YourAmazonAccessKey',
'YourAmazonSecretKey'
);
$configuration->setSignatureMethod('v4');
$configuration->setUseDualstackUrl(true);
-$connector = new \Akeeba\Engine\Postproc\Connector\S3v4\Connector($configuration);
+$connector = new \Akeeba\S3\Connector($configuration);
```
### HTTPS vs plain HTTP
```php
// DigitalOcean Spaces using v4 signatures
// The access credentials are those used in the example at https://developers.digitalocean.com/documentation/spaces/
-$configuration = new \Akeeba\Engine\Postproc\Connector\S3v4\Configuration(
+$configuration = new \Akeeba\S3\Configuration(
'532SZONTQ6ALKBCU94OU',
'zCkY83KVDXD8u83RouEYPKEm/dhPSPB45XsfnWj8fxQ',
'v4',
);
$configuration->setEndpoint('nyc3.digitaloceanspaces.com');
-$connector = new \Akeeba\Engine\Postproc\Connector\S3v4\Connector($configuration);
+$connector = new \Akeeba\S3\Connector($configuration);
```
If your S3-compatible API uses v2 signatures you do not need to specify a region.
```php
// DigitalOcean Spaces using v2 signatures
// The access credentials are those used in the example at https://developers.digitalocean.com/documentation/spaces/
-$configuration = new \Akeeba\Engine\Postproc\Connector\S3v4\Configuration(
+$configuration = new \Akeeba\S3\Configuration(
'532SZONTQ6ALKBCU94OU',
'zCkY83KVDXD8u83RouEYPKEm/dhPSPB45XsfnWj8fxQ',
'v2'
);
$configuration->setEndpoint('nyc3.digitaloceanspaces.com');
-$connector = new \Akeeba\Engine\Postproc\Connector\S3v4\Connector($configuration);
+$connector = new \Akeeba\S3\Connector($configuration);
```
### Legacy path-style access
$configuration->setUseLegacyPathStyle(true);
```
-Caveat: this will not work with v2 signatures if you are using Amazon AWS S3 proper. It will work with the v2 signatures if you are using a custom endpoint, though. In fact, most S3-compatible APIs implementing V2 signatures _expect_ you to use path-style access.
+Caveat: this will not work with v2 signatures if you are using Amazon AWS S3 proper. It will very likely work with the v2 signatures if you are using a custom endpoint, though.
### Dualstack (IPv4 and IPv6) support
+++ /dev/null
-Need to check:
-
-endpoint in [amazon, custom]
-signature in [v2, v4]
-path style in [true, false]
- upload
- download
- presigned URL generation
- presigned URL access
-
-
-USING VIRTUAL HOSTING, v4 SIGNATURES
- presigned URL must use s3.amazonaws.com i.e. path-style hosting (because who needs logic?)
\ No newline at end of file
"type": "library",
"description": "A compact, dependency-less Amazon S3 API client implementing the most commonly used features",
"require": {
- "php": ">=7.1.0 <8.1",
+ "php": ">=7.1.0 <8.4",
"ext-curl": "*",
"ext-simplexml": "*"
},
"s3"
],
"homepage": "https://github.com/akeeba/s3",
- "license": "GPL-3.0+",
+ "license": "GPL-3.0-or-later",
"authors": [
{
"name": "Nicholas K. Dionysopoulos",
],
"autoload": {
"psr-4": {
- "Akeeba\\Engine\\Postproc\\Connector\\S3v4\\": "src"
- }
+ "Akeeba\\S3\\": "src"
+ },
+ "files": [
+ "src/aliasing.php"
+ ]
+ },
+ "archive": {
+ "exclude": [
+ "minitest",
+ "TODO.md"
+ ]
}
}
{
- "_readme": [
- "This file locks the dependencies of your project to a known state",
- "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
- "This file is @generated automatically"
- ],
- "content-hash": "1070071b351d45a80934e854f0725d64",
- "packages": [],
- "packages-dev": [],
- "aliases": [],
- "minimum-stability": "stable",
- "stability-flags": [],
- "prefer-stable": false,
- "prefer-lowest": false,
- "platform": {
- "php": ">=5.3.4"
- },
- "platform-dev": []
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "27f387a657b2784510b177f73c436346",
+ "packages": [],
+ "packages-dev": [],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": [],
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": {
+ "php": ">=7.1.0 <8.4",
+ "ext-curl": "*",
+ "ext-simplexml": "*"
+ },
+ "platform-dev": [],
+ "plugin-api-version": "2.3.0"
}
--- /dev/null
+# Testing notes
+
+## Against Amazon S3 proper
+
+This is the _canonical_ method for testing this library since Amazon S3 proper is the canonical provider of the S3 API (and not all of its quirks are fully documented, we might add).
+
+Copy `config.dist.php` to `config.php` and enter the connection information to your Amazon S3 or compatible service.
+
+## Against [LocalStack](https://localstack.cloud)
+
+This method is very useful for development.
+
+Install LocalStack [as per their documentation](https://docs.localstack.cloud/getting-started/installation/).
+
+You will also need to install [`awslocal`](https://github.com/localstack/awscli-local) like so:
+```php
+pip install awscli
+pip install awscli-local
+```
+
+Start LocalStack e.g. `localstack start -d`
+
+Create a new bucket called `test` i.e. `awslocal s3 mk s3://test`
+
+Copy `config.dist.php` to `config.php` and make the following changes:
+```php
+ define('DEFAULT_ENDPOINT', 'localhost.localstack.cloud:4566');
+ define('DEFAULT_ACCESS_KEY', 'ANYRANDOMSTRINGWILLDO');
+ define('DEFAULT_SECRET_KEY', 'ThisIsAlwaysIgnoredByLocalStack');
+ define('DEFAULT_REGION', 'us-east-1');
+ define('DEFAULT_BUCKET', 'test');
+ define('DEFAULT_SIGNATURE', 'v4');
+ define('DEFAULT_PATH_ACCESS', true);
+```
+
+Note that single- and dualstack tests result in the same URLs for all S3-compatible services, including LocalStack. These tests are essentially duplicates in this use case.
+
+## Against Wasabi
+
+Wasabi nominally supports v4 signatures, but their implementation is actually _non-canonical_, as they only read the date from the optional `x-amz-date` header, without falling back to the standard HTTP `Date` header. We have added a workaround for this behaviour which necessitates testing with it.
+
+Just like with Amazon S3 proper, copy `config.dist.php` to `config.php` and enter the connection information to your Wasabi storage. You will also need to set up the custom endpoint like so:
+```php
+define('DEFAULT_ENDPOINT', 's3.eu-central-2.wasabisys.com');
+```
+
+**IMPORTANT!** The above endpoint will be different, depending on which region you've created your bucket in. The example above assumes the `eu-central-2` region. If you use the wrong region the tests _will_ fail!
+
+## Against Synology C2
+
+Synology C2 is an S3-“compatible” storage service. It is not very “compatible” though, since they implemented Amazon's documentation of the v4 signatures instead of how the v4 signatures work in the real world (yeah, there's a very big difference). While Amazon S3 _in reality_ expects all dates to be formatted as per RFC1123, they document that they expect them to be formatted as per “ISO 8601” and they give their _completely wrong_ interpretation of what the “ISO 8601” format is. Synology did not catch that discrepancy, and they only expect the wrongly formatted dates which is totally NOT what S3 itself expects. Luckily, most third party implementations expect either format because they've caught the discrepancy between documentation and reality, therefore making it possible for us to come up with a viable workaround.
+
+And that's why we need to test with C2 as well, folks.
+
+Copy `config.dist.php` to `config.php` and enter the connection information to your Synology S3 service.
+
+It is very important to note two things:
+```php
+define('DEFAULT_ENDPOINT', 'eu-002.s3.synologyc2.net');
+define('DEFAULT_REGION', 'eu-002');
+```
+The endpoint URL is given in the Synology C2 Object Manager, next to each bucket. Note the part before `.s3.`. This is the **region** you need to use with v4 signatures. They do not document this anywhere.
\ No newline at end of file
* 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\MiniTest\Test;
-use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
+use Akeeba\S3\Connector;
use RuntimeException;
abstract class AbstractTest
*/
protected static function createFile(int $size = AbstractTest::SIX_HUNDRED_KB, int $blockSize = self::BLOCK_SIZE, bool $reuseBlock = true)
{
- $tempFilePath = tempnam(self::getTempFolder(), 'as3');
+ $tempFilePath = tempnam(static::getTempFolder(), 'as3');
if ($tempFilePath === false)
{
throw new RuntimeException("Cannot create a temporary file.");
}
- $fp = @fopen($tempFilePath, 'wb', false);
+ $fp = @fopen($tempFilePath, 'w', false);
if ($fp === false)
{
throw new RuntimeException("Cannot write to the temporary file.");
}
- $blockSize = self::BLOCK_SIZE;
+ $blockSize = static::BLOCK_SIZE;
$lastBlockSize = $size % $blockSize;
$wholeBlocks = (int) (($size - $lastBlockSize) / $blockSize);
- $blockData = self::getRandomData();
+ $blockData = static::getRandomData();
for ($i = 0; $i < $wholeBlocks; $i++)
{
if (!$reuseBlock)
{
- $blockData = self::getRandomData($blockSize);
+ $blockData = static::getRandomData($blockSize);
}
}
return false;
}
- return hash_file(self::FILE_HASHING_ALGORITHM, $referenceFilePath) === hash_file(self::FILE_HASHING_ALGORITHM, $unknownFilePath);
+ return hash_file(static::FILE_HASHING_ALGORITHM, $referenceFilePath) === hash_file(static::FILE_HASHING_ALGORITHM, $unknownFilePath);
}
/**
* 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\MiniTest\Test;
-use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
-use Akeeba\Engine\Postproc\Connector\S3v4\Input;
+use Akeeba\S3\Connector;
+use Akeeba\S3\Input;
/**
* Upload, download and delete big files (over 1MB), without multipart uploads. Uses string or file sources.
/**
* Number of uploaded chunks.
*
- * This is set by self::upload(). Zero for single part uploads, non-zero for multipart uploads.
+ * This is set by static::upload(). Zero for single part uploads, non-zero for multipart uploads.
*
* @var int
*/
public static function upload5MBString(Connector $s3, array $options): bool
{
- return self::upload($s3, $options, self::FIVE_MB, 'bigtest_5mb.dat');
+ return static::upload($s3, $options, static::FIVE_MB, 'bigtest_5mb.dat');
}
public static function upload6MBString(Connector $s3, array $options): bool
{
- return self::upload($s3, $options, self::SIX_MB, 'bigtest_6mb.dat');
+ return static::upload($s3, $options, static::SIX_MB, 'bigtest_6mb.dat');
}
public static function upload10MBString(Connector $s3, array $options): bool
{
- return self::upload($s3, $options, self::TEN_MB, 'bigtest_10mb.dat');
+ return static::upload($s3, $options, static::TEN_MB, 'bigtest_10mb.dat');
}
public static function upload11MBString(Connector $s3, array $options): bool
{
- return self::upload($s3, $options, self::ELEVEN_MB, 'bigtest_11mb.dat');
+ return static::upload($s3, $options, static::ELEVEN_MB, 'bigtest_11mb.dat');
}
public static function upload5MBFile(Connector $s3, array $options): bool
{
- return self::upload($s3, $options, self::FIVE_MB, 'bigtest_5mb.dat', false);
+ return static::upload($s3, $options, static::FIVE_MB, 'bigtest_5mb.dat', false);
}
public static function upload6MBFile(Connector $s3, array $options): bool
{
- return self::upload($s3, $options, self::SIX_MB, 'bigtest_6mb.dat', false);
+ return static::upload($s3, $options, static::SIX_MB, 'bigtest_6mb.dat', false);
}
public static function upload10MBFile(Connector $s3, array $options): bool
{
- return self::upload($s3, $options, self::TEN_MB, 'bigtest_10mb.dat', false);
+ return static::upload($s3, $options, static::TEN_MB, 'bigtest_10mb.dat', false);
}
public static function upload11MBFile(Connector $s3, array $options): bool
{
- return self::upload($s3, $options, self::ELEVEN_MB, 'bigtest_11mb.dat', false);
+ return static::upload($s3, $options, static::ELEVEN_MB, 'bigtest_11mb.dat', false);
}
protected static function upload(Connector $s3, array $options, int $size, string $uri, bool $useString = true): bool
$dotPos = strrpos($uri, '.');
$uri = substr($uri, 0, $dotPos) . '.' . md5(microtime(false)) . substr($uri, $dotPos);
- self::$numberOfChunks = 0;
+ static::$numberOfChunks = 0;
if ($useString)
{
- $sourceData = self::getRandomData($size);
+ $sourceData = static::getRandomData($size);
$input = Input::createFromData($sourceData);
}
else
{
// Create a file with random data
- $sourceFile = self::createFile($size);
+ $sourceFile = static::createFile($size);
$input = Input::createFromFile($sourceFile);
}
// Upload the file. Throws exception if it fails.
$bucket = $options['bucket'];
- if (!self::$multipart)
+ if (!static::$multipart)
{
$s3->putObject($input, $bucket, $uri);
}
$input->setEtags($eTags);
$input->setPartNumber($partNumber);
- $etag = $s3->uploadMultipart($input, $bucket, $uri, [], self::$uploadChunkSize);
+ $etag = $s3->uploadMultipart($input, $bucket, $uri, [], static::$uploadChunkSize);
// If the result was null we have no more file parts to process.
if (is_null($etag))
$partNumber++;
}
- self::$numberOfChunks = count($eTags);
+ static::$numberOfChunks = count($eTags);
// Finalize the multipart upload. Tells Amazon to construct the file from the uploaded parts.
$s3->finalizeMultipart($input, $bucket, $uri);
$result = true;
// Should I download the file and compare its contents?
- if (self::$downloadAfter)
+ if (static::$downloadAfter)
{
if ($useString)
{
$downloadedData = $s3->getObject($bucket, $uri);
// Compare the file contents.
- $result = self::areStringsEqual($sourceData, $downloadedData);
+ $result = static::areStringsEqual($sourceData, $downloadedData);
}
else
{
// Download the data. Throws exception if it fails.
- $downloadedFile = tempnam(self::getTempFolder(), 'as3');
+ $downloadedFile = tempnam(static::getTempFolder(), 'as3');
$s3->getObject($bucket, $uri, $downloadedFile);
// Compare the file contents.
- $result = self::areFilesEqual($sourceFile, $downloadedFile);
+ $result = static::areFilesEqual($sourceFile, $downloadedFile);
@unlink($downloadedFile);
}
}
// Should I delete the remotely stored file?
- if (self::$deleteRemote)
+ if (static::$deleteRemote)
{
// Delete the remote file. Throws exception if it fails.
$s3->deleteObject($bucket, $uri);
* 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\MiniTest\Test;
-use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
+use Akeeba\S3\Connector;
class BucketLocation extends AbstractTest
{
{
$location = $s3->getBucketLocation($options['bucket']);
- self::assert($location === $options['region'], "Bucket ‘{$options['bucket']}′ reports being in region ‘{$location}′ instead of expected ‘{$options['region']}′");
+ static::assert($location === $options['region'], "Bucket ‘{$options['bucket']}′ reports being in region ‘{$location}′ instead of expected ‘{$options['region']}′");
return true;
}
* 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\MiniTest\Test;
-use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
+use Akeeba\S3\Connector;
use RuntimeException;
class BucketsList extends AbstractTest
{
$buckets = $s3->listBuckets(true);
- self::assert(is_array($buckets), "Detailed buckets list is not an array");
- self::assert(isset($buckets['owner']), "Detailed buckets list does not list an owner");
- self::assert(isset($buckets['owner']['id']), "Detailed buckets list does not list an owner's id");
- self::assert(isset($buckets['owner']['name']), "Detailed buckets list does not list an owner's name");
- self::assert(isset($buckets['buckets']), "Detailed buckets list does not list any buckets");
+ static::assert(is_array($buckets), "Detailed buckets list is not an array");
+ static::assert(isset($buckets['owner']), "Detailed buckets list does not list an owner");
+ static::assert(isset($buckets['owner']['id']), "Detailed buckets list does not list an owner's id");
+ static::assert(isset($buckets['owner']['name']), "Detailed buckets list does not list an owner's name");
+ static::assert(isset($buckets['buckets']), "Detailed buckets list does not list any buckets");
foreach ($buckets['buckets'] as $bucketInfo)
{
- self::assert(isset($bucketInfo['name']), "Bucket information does not list a name");
- self::assert(isset($bucketInfo['time']), "Bucket information does not list a created times");
+ static::assert(isset($bucketInfo['name']), "Bucket information does not list a name");
+ static::assert(isset($bucketInfo['time']), "Bucket information does not list a created times");
if ($bucketInfo['name'] === $options['bucket'])
{
{
$buckets = $s3->listBuckets(false);
- self::assert(is_array($buckets), "Simple buckets list is not an array");
- self::assert(in_array($options['bucket'], $buckets), "Simple buckets list does not include configured bucket ‘{$options['bucket']}′");
+ static::assert(is_array($buckets), "Simple buckets list is not an array");
+ static::assert(in_array($options['bucket'], $buckets), "Simple buckets list does not include configured bucket ‘{$options['bucket']}′");
return true;
}
--- /dev/null
+<?php
+/**
+ * Akeeba Engine
+ *
+ * @package akeebaengine
+ * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @license GNU General Public License version 3, or later
+ */
+
+
+namespace Akeeba\MiniTest\Test;
+
+
+use Akeeba\S3\Connector;
+use Akeeba\S3\Exception\CannotDeleteFile;
+use Akeeba\S3\Exception\CannotGetFile;
+use Akeeba\S3\Input;
+
+class HeadObject extends AbstractTest
+{
+ public static function testExistingFile(Connector $s3, array $options): bool
+ {
+ $uri = 'head_test.dat';
+
+ // Randomize the name. Required for archive buckets where you cannot overwrite data.
+ $dotPos = strrpos($uri, '.');
+ $uri = substr($uri, 0, $dotPos) . '.' . md5(microtime(false)) . substr($uri, $dotPos);
+
+ // Create a file with random data
+ $sourceFile = static::createFile(AbstractTest::TEN_KB);
+
+ // Upload the file. Throws exception if it fails.
+ $bucket = $options['bucket'];
+ $input = Input::createFromFile($sourceFile);
+
+ $s3->putObject($input, $bucket, $uri);
+
+ $headers = $s3->headObject($bucket, $uri);
+
+ static::assert(isset($headers['size']), 'The returned headers do not contain the object size');
+ static::assert($headers['size'] == AbstractTest::TEN_KB, 'The returned size does not match');
+
+ // Remove the local files
+ @unlink($sourceFile);
+
+ // Delete the remote file. Throws exception if it fails.
+ $s3->deleteObject($bucket, $uri);
+
+ return true;
+ }
+
+ public static function testMissingFile(Connector $s3, array $options): bool
+ {
+ $bucket = $options['bucket'];
+
+ try
+ {
+ $headers = $s3->headObject($bucket, md5(microtime(false)) . '_does_not_exist');
+ }
+ catch (CannotGetFile $e)
+ {
+ return true;
+ }
+
+ return false;
+ }
+}
\ No newline at end of file
* 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\MiniTest\Test;
-use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
-use Akeeba\Engine\Postproc\Connector\S3v4\Exception\CannotPutFile;
-use Akeeba\Engine\Postproc\Connector\S3v4\Input;
+use Akeeba\S3\Connector;
+use Akeeba\S3\Exception\CannotPutFile;
+use Akeeba\S3\Input;
class ListFiles extends AbstractTest
{
public static function setup(Connector $s3, array $options): void
{
- $data = self::getRandomData(self::TEN_KB);
+ $data = static::getRandomData(static::TEN_KB);
- foreach (self::$paths as $uri)
+ foreach (static::$paths as $uri)
{
$input = Input::createFromData($data);
try
public static function teardown(Connector $s3, array $options): void
{
- foreach (self::$paths as $uri)
+ foreach (static::$paths as $uri)
{
try
{
{
$listing = $s3->getBucket($options['bucket'], 'listtest_');
- self::assert(is_array($listing), "The files listing must be an array");
- self::assert(count($listing) == 3, "I am expecting to see 3 files");
+ static::assert(is_array($listing), "The files listing must be an array");
+ static::assert(count($listing) == 3, "I am expecting to see 3 files");
// Make sure I have the expected files
- self::assert(array_key_exists('listtest_one.dat', $listing), "File listtest_one.dat not in listing");
- self::assert(array_key_exists('listtest_two.dat', $listing), "File listtest_two.dat not in listing");
- self::assert(array_key_exists('listtest_three.dat', $listing), "File listtest_three.dat not in listing");
+ static::assert(array_key_exists('listtest_one.dat', $listing), "File listtest_one.dat not in listing");
+ static::assert(array_key_exists('listtest_two.dat', $listing), "File listtest_two.dat not in listing");
+ static::assert(array_key_exists('listtest_three.dat', $listing), "File listtest_three.dat not in listing");
// I must not see the files in subdirectories
- self::assert(!array_key_exists('listtest_four.dat', $listing), "File listtest_four.dat in listing");
- self::assert(!array_key_exists('listtest_five.dat', $listing), "File listtest_five.dat in listing");
- self::assert(!array_key_exists('listtest_six.dat', $listing), "File listtest_six.dat in listing");
+ static::assert(!array_key_exists('listtest_four.dat', $listing), "File listtest_four.dat in listing");
+ static::assert(!array_key_exists('listtest_five.dat', $listing), "File listtest_five.dat in listing");
+ static::assert(!array_key_exists('listtest_six.dat', $listing), "File listtest_six.dat in listing");
// I must not see the files not matching the prefix I gave
- self::assert(!array_key_exists('spam.dat', $listing), "File spam.dat in listing");
- self::assert(!array_key_exists('ham.dat', $listing), "File ham.dat in listing");
+ static::assert(!array_key_exists('spam.dat', $listing), "File spam.dat in listing");
+ static::assert(!array_key_exists('ham.dat', $listing), "File ham.dat in listing");
foreach ($listing as $fileName => $info)
{
- self::assert(isset($info['name']), "File entries must have a name");
- self::assert(isset($info['time']), "File entries must have a time");
- self::assert(isset($info['size']), "File entries must have a size");
- self::assert(isset($info['hash']), "File entries must have a hash");
+ static::assert(isset($info['name']), "File entries must have a name");
+ static::assert(isset($info['time']), "File entries must have a time");
+ static::assert(isset($info['size']), "File entries must have a size");
+ static::assert(isset($info['hash']), "File entries must have a hash");
}
return true;
{
$listing = $s3->getBucket($options['bucket'], 'listtest_', null, 1);
- self::assert(is_array($listing), "The files listing must be an array");
- self::assert(count($listing) == 1, sprintf("I am expecting to see 1 file, %s seen", count($listing)));
+ static::assert(is_array($listing), "The files listing must be an array");
+ static::assert(count($listing) == 1, sprintf("I am expecting to see 1 file, %s seen", count($listing)));
$files = array_keys($listing);
$continued = $s3->getBucket($options['bucket'], 'listtest_', array_shift($files));
- self::assert(is_array($continued), "The continued files listing must be an array");
- self::assert(count($continued) == 2, sprintf("I am expecting to see 2 files, %s seen", count($continued)));
+ static::assert(is_array($continued), "The continued files listing must be an array");
+ static::assert(count($continued) == 2, sprintf("I am expecting to see 2 files, %s seen", count($continued)));
$listing = array_merge($listing, $continued);
// Make sure I have the expected files
- self::assert(array_key_exists('listtest_one.dat', $listing), "File listtest_one.dat not in listing");
- self::assert(array_key_exists('listtest_two.dat', $listing), "File listtest_two.dat not in listing");
- self::assert(array_key_exists('listtest_three.dat', $listing), "File listtest_three.dat not in listing");
+ static::assert(array_key_exists('listtest_one.dat', $listing), "File listtest_one.dat not in listing");
+ static::assert(array_key_exists('listtest_two.dat', $listing), "File listtest_two.dat not in listing");
+ static::assert(array_key_exists('listtest_three.dat', $listing), "File listtest_three.dat not in listing");
// I must not see the files in subdirectories
- self::assert(!array_key_exists('listtest_four.dat', $listing), "File listtest_four.dat in listing");
- self::assert(!array_key_exists('listtest_five.dat', $listing), "File listtest_five.dat in listing");
- self::assert(!array_key_exists('listtest_six.dat', $listing), "File listtest_six.dat in listing");
+ static::assert(!array_key_exists('listtest_four.dat', $listing), "File listtest_four.dat in listing");
+ static::assert(!array_key_exists('listtest_five.dat', $listing), "File listtest_five.dat in listing");
+ static::assert(!array_key_exists('listtest_six.dat', $listing), "File listtest_six.dat in listing");
// I must not see the files not matching the prefix I gave
- self::assert(!array_key_exists('spam.dat', $listing), "File spam.dat in listing");
- self::assert(!array_key_exists('ham.dat', $listing), "File ham.dat in listing");
+ static::assert(!array_key_exists('spam.dat', $listing), "File spam.dat in listing");
+ static::assert(!array_key_exists('ham.dat', $listing), "File ham.dat in listing");
foreach ($listing as $fileName => $info)
{
- self::assert(isset($info['name']), "File entries must have a name");
- self::assert(isset($info['time']), "File entries must have a time");
- self::assert(isset($info['size']), "File entries must have a size");
- self::assert(isset($info['hash']), "File entries must have a hash");
+ static::assert(isset($info['name']), "File entries must have a name");
+ static::assert(isset($info['time']), "File entries must have a time");
+ static::assert(isset($info['size']), "File entries must have a size");
+ static::assert(isset($info['hash']), "File entries must have a hash");
}
return true;
{
$listing = $s3->getBucket($options['bucket'], 'list_deeper/test_');
- self::assert(is_array($listing), "The files listing must be an array");
- self::assert(count($listing) == 3, "I am expecting to see 3 files");
+ static::assert(is_array($listing), "The files listing must be an array");
+ static::assert(count($listing) == 3, "I am expecting to see 3 files");
// Make sure I have the expected files
- self::assert(array_key_exists('list_deeper/test_one.dat', $listing), "File test_one.dat not in listing");
- self::assert(array_key_exists('list_deeper/test_two.dat', $listing), "File test_two.dat not in listing");
- self::assert(array_key_exists('list_deeper/test_three.dat', $listing), "File test_three.dat not in listing");
+ static::assert(array_key_exists('list_deeper/test_one.dat', $listing), "File test_one.dat not in listing");
+ static::assert(array_key_exists('list_deeper/test_two.dat', $listing), "File test_two.dat not in listing");
+ static::assert(array_key_exists('list_deeper/test_three.dat', $listing), "File test_three.dat not in listing");
// I must not see the files with different prefix
- self::assert(!array_key_exists('list_deeper/listtest_four.dat', $listing), "File listtest_four.dat in listing");
- self::assert(!array_key_exists('list_deeper/listtest_five.dat', $listing), "File listtest_five.dat in listing");
- self::assert(!array_key_exists('list_deeper/listtest_six.dat', $listing), "File listtest_six.dat in listing");
- self::assert(!array_key_exists('list_deeper/spam.dat', $listing), "File spam.dat in listing");
+ static::assert(!array_key_exists('list_deeper/listtest_four.dat', $listing), "File listtest_four.dat in listing");
+ static::assert(!array_key_exists('list_deeper/listtest_five.dat', $listing), "File listtest_five.dat in listing");
+ static::assert(!array_key_exists('list_deeper/listtest_six.dat', $listing), "File listtest_six.dat in listing");
+ static::assert(!array_key_exists('list_deeper/spam.dat', $listing), "File spam.dat in listing");
// I must not see the files in subdirectories
- self::assert(!array_key_exists('list_deeper/listtest_deeper/seven.dat', $listing), "File spam.dat in listing");
- self::assert(!array_key_exists('list_deeper/listtest_deeper/eight.dat', $listing), "File spam.dat in listing");
+ static::assert(!array_key_exists('list_deeper/listtest_deeper/seven.dat', $listing), "File spam.dat in listing");
+ static::assert(!array_key_exists('list_deeper/listtest_deeper/eight.dat', $listing), "File spam.dat in listing");
foreach ($listing as $fileName => $info)
{
- self::assert(isset($info['name']), "File entries must have a name");
- self::assert(isset($info['time']), "File entries must have a time");
- self::assert(isset($info['size']), "File entries must have a size");
- self::assert(isset($info['hash']), "File entries must have a hash");
+ static::assert(isset($info['name']), "File entries must have a name");
+ static::assert(isset($info['time']), "File entries must have a time");
+ static::assert(isset($info['size']), "File entries must have a size");
+ static::assert(isset($info['hash']), "File entries must have a hash");
}
return true;
{
$listing = $s3->getBucket($options['bucket'], 'list_deeper/test_', null, 1);
- self::assert(is_array($listing), "The files listing must be an array");
- self::assert(count($listing) == 1, sprintf("I am expecting to see 1 file, %s seen", count($listing)));
+ static::assert(is_array($listing), "The files listing must be an array");
+ static::assert(count($listing) == 1, sprintf("I am expecting to see 1 file, %s seen", count($listing)));
$files = array_keys($listing);
$continued = $s3->getBucket($options['bucket'], 'list_deeper/test_', array_shift($files));
- self::assert(is_array($continued), "The continued files listing must be an array");
- self::assert(count($continued) == 2, sprintf("I am expecting to see 2 files, %s seen", count($continued)));
+ static::assert(is_array($continued), "The continued files listing must be an array");
+ static::assert(count($continued) == 2, sprintf("I am expecting to see 2 files, %s seen", count($continued)));
$listing = array_merge($listing, $continued);
- self::assert(is_array($listing), "The files listing must be an array");
- self::assert(count($listing) == 3, "I am expecting to see 3 files");
+ static::assert(is_array($listing), "The files listing must be an array");
+ static::assert(count($listing) == 3, "I am expecting to see 3 files");
// Make sure I have the expected files
- self::assert(array_key_exists('list_deeper/test_one.dat', $listing), "File test_one.dat not in listing");
- self::assert(array_key_exists('list_deeper/test_two.dat', $listing), "File test_two.dat not in listing");
- self::assert(array_key_exists('list_deeper/test_three.dat', $listing), "File test_three.dat not in listing");
+ static::assert(array_key_exists('list_deeper/test_one.dat', $listing), "File test_one.dat not in listing");
+ static::assert(array_key_exists('list_deeper/test_two.dat', $listing), "File test_two.dat not in listing");
+ static::assert(array_key_exists('list_deeper/test_three.dat', $listing), "File test_three.dat not in listing");
// I must not see the files with different prefix
- self::assert(!array_key_exists('list_deeper/listtest_four.dat', $listing), "File listtest_four.dat in listing");
- self::assert(!array_key_exists('list_deeper/listtest_five.dat', $listing), "File listtest_five.dat in listing");
- self::assert(!array_key_exists('list_deeper/listtest_six.dat', $listing), "File listtest_six.dat in listing");
- self::assert(!array_key_exists('list_deeper/spam.dat', $listing), "File spam.dat in listing");
+ static::assert(!array_key_exists('list_deeper/listtest_four.dat', $listing), "File listtest_four.dat in listing");
+ static::assert(!array_key_exists('list_deeper/listtest_five.dat', $listing), "File listtest_five.dat in listing");
+ static::assert(!array_key_exists('list_deeper/listtest_six.dat', $listing), "File listtest_six.dat in listing");
+ static::assert(!array_key_exists('list_deeper/spam.dat', $listing), "File spam.dat in listing");
// I must not see the files in subdirectories
- self::assert(!array_key_exists('list_deeper/listtest_deeper/seven.dat', $listing), "File spam.dat in listing");
- self::assert(!array_key_exists('list_deeper/listtest_deeper/eight.dat', $listing), "File spam.dat in listing");
+ static::assert(!array_key_exists('list_deeper/listtest_deeper/seven.dat', $listing), "File spam.dat in listing");
+ static::assert(!array_key_exists('list_deeper/listtest_deeper/eight.dat', $listing), "File spam.dat in listing");
foreach ($listing as $fileName => $info)
{
- self::assert(isset($info['name']), "File entries must have a name");
- self::assert(isset($info['time']), "File entries must have a time");
- self::assert(isset($info['size']), "File entries must have a size");
- self::assert(isset($info['hash']), "File entries must have a hash");
+ static::assert(isset($info['name']), "File entries must have a name");
+ static::assert(isset($info['time']), "File entries must have a time");
+ static::assert(isset($info['size']), "File entries must have a size");
+ static::assert(isset($info['hash']), "File entries must have a hash");
}
return true;
*/
$listing = $s3->getBucket($options['bucket'], 'list_deeper/listtest_', null, 1);
- self::assert(is_array($listing), "The files listing must be an array");
- self::assert(count($listing) == 1, sprintf("I am expecting to see 1 files, %s seen", count($listing)));
+ static::assert(is_array($listing), "The files listing must be an array");
+ static::assert(count($listing) == 1, sprintf("I am expecting to see 1 files, %s seen", count($listing)));
$files = array_keys($listing);
$continued = $s3->getBucket($options['bucket'], 'list_deeper/listtest_', array_shift($files));
- self::assert(is_array($continued), "The continued files listing must be an array");
- self::assert(count($continued) == 2, sprintf("I am expecting to see 2 files, %s seen", count($continued)));
+ static::assert(is_array($continued), "The continued files listing must be an array");
+ static::assert(count($continued) == 2, sprintf("I am expecting to see 2 files, %s seen", count($continued)));
$listing = array_merge($listing, $continued);
- self::assert(is_array($listing), "The files listing must be an array");
- self::assert(count($listing) == 3, "I am expecting to see 3 files");
+ static::assert(is_array($listing), "The files listing must be an array");
+ static::assert(count($listing) == 3, "I am expecting to see 3 files");
// Make sure I have the expected files
- self::assert(array_key_exists('list_deeper/listtest_four.dat', $listing), "File listtest_four.dat not in listing");
- self::assert(array_key_exists('list_deeper/listtest_five.dat', $listing), "File listtest_five.dat not in listing");
- self::assert(array_key_exists('list_deeper/listtest_six.dat', $listing), "File listtest_six.dat not in listing");
+ static::assert(array_key_exists('list_deeper/listtest_four.dat', $listing), "File listtest_four.dat not in listing");
+ static::assert(array_key_exists('list_deeper/listtest_five.dat', $listing), "File listtest_five.dat not in listing");
+ static::assert(array_key_exists('list_deeper/listtest_six.dat', $listing), "File listtest_six.dat not in listing");
// I must not see the files with different prefix
- self::assert(!array_key_exists('list_deeper/test_one.dat', $listing), "File test_one.dat in listing");
- self::assert(!array_key_exists('list_deeper/test_two.dat', $listing), "File test_two.dat in listing");
- self::assert(!array_key_exists('list_deeper/test_three.dat', $listing), "File test_three.dat in listing");
- self::assert(!array_key_exists('list_deeper/spam.dat', $listing), "File spam.dat in listing");
+ static::assert(!array_key_exists('list_deeper/test_one.dat', $listing), "File test_one.dat in listing");
+ static::assert(!array_key_exists('list_deeper/test_two.dat', $listing), "File test_two.dat in listing");
+ static::assert(!array_key_exists('list_deeper/test_three.dat', $listing), "File test_three.dat in listing");
+ static::assert(!array_key_exists('list_deeper/spam.dat', $listing), "File spam.dat in listing");
// I must not see the files in subdirectories
- self::assert(!array_key_exists('list_deeper/listtest_deeper/seven.dat', $listing), "File spam.dat in listing");
- self::assert(!array_key_exists('list_deeper/listtest_deeper/eight.dat', $listing), "File spam.dat in listing");
+ static::assert(!array_key_exists('list_deeper/listtest_deeper/seven.dat', $listing), "File spam.dat in listing");
+ static::assert(!array_key_exists('list_deeper/listtest_deeper/eight.dat', $listing), "File spam.dat in listing");
foreach ($listing as $fileName => $info)
{
- self::assert(isset($info['name']), "File entries must have a name");
- self::assert(isset($info['time']), "File entries must have a time");
- self::assert(isset($info['size']), "File entries must have a size");
- self::assert(isset($info['hash']), "File entries must have a hash");
+ static::assert(isset($info['name']), "File entries must have a name");
+ static::assert(isset($info['time']), "File entries must have a time");
+ static::assert(isset($info['size']), "File entries must have a size");
+ static::assert(isset($info['hash']), "File entries must have a hash");
}
return true;
{
$listing = $s3->getBucket($options['bucket'], 'list_deeper/listtest_', null, null, '/', true);
- self::assert(is_array($listing), "The files listing must be an array");
- self::assert(count($listing) == 4, sprintf("I am expecting to see 4 entries, %s entries seen.", count($listing)));
+ static::assert(is_array($listing), "The files listing must be an array");
+ static::assert(count($listing) == 4, sprintf("I am expecting to see 4 entries, %s entries seen.", count($listing)));
// Make sure I have the expected files
- self::assert(array_key_exists('list_deeper/listtest_four.dat', $listing), "File listtest_four.dat not in listing");
- self::assert(array_key_exists('list_deeper/listtest_five.dat', $listing), "File listtest_five.dat not in listing");
- self::assert(array_key_exists('list_deeper/listtest_six.dat', $listing), "File listtest_six.dat not in listing");
- self::assert(array_key_exists('list_deeper/listtest_deeper/', $listing), "Folder listtest_deeper not in listing");
+ static::assert(array_key_exists('list_deeper/listtest_four.dat', $listing), "File listtest_four.dat not in listing");
+ static::assert(array_key_exists('list_deeper/listtest_five.dat', $listing), "File listtest_five.dat not in listing");
+ static::assert(array_key_exists('list_deeper/listtest_six.dat', $listing), "File listtest_six.dat not in listing");
+ static::assert(array_key_exists('list_deeper/listtest_deeper/', $listing), "Folder listtest_deeper not in listing");
// I must not see the files in subdirectories
- self::assert(!array_key_exists('list_deeper/listtest_deeper/seven.dat', $listing), "File seven.dat in listing");
- self::assert(!array_key_exists('list_deeper/listtest_deeper/eight.dat', $listing), "File eight.dat in listing");
+ static::assert(!array_key_exists('list_deeper/listtest_deeper/seven.dat', $listing), "File seven.dat in listing");
+ static::assert(!array_key_exists('list_deeper/listtest_deeper/eight.dat', $listing), "File eight.dat in listing");
// I must not see the files with different prefix
- self::assert(!array_key_exists('list_deeper/spam.dat', $listing), "File spam.dat in listing");
- self::assert(!array_key_exists('list_deeper/test_one.dat', $listing), "File test_one.dat not in listing");
- self::assert(!array_key_exists('list_deeper/test_two.dat', $listing), "File test_two.dat not in listing");
- self::assert(!array_key_exists('list_deeper/test_three.dat', $listing), "File test_three.dat not in listing");
+ static::assert(!array_key_exists('list_deeper/spam.dat', $listing), "File spam.dat in listing");
+ static::assert(!array_key_exists('list_deeper/test_one.dat', $listing), "File test_one.dat not in listing");
+ static::assert(!array_key_exists('list_deeper/test_two.dat', $listing), "File test_two.dat not in listing");
+ static::assert(!array_key_exists('list_deeper/test_three.dat', $listing), "File test_three.dat not in listing");
foreach ($listing as $fileName => $info)
{
if (substr($fileName, -1) !== '/')
{
- self::assert(isset($info['name']), "File entries must have a name");
- self::assert(isset($info['time']), "File entries must have a time");
- self::assert(isset($info['size']), "File entries must have a size");
- self::assert(isset($info['hash']), "File entries must have a hash");
+ static::assert(isset($info['name']), "File entries must have a name");
+ static::assert(isset($info['time']), "File entries must have a time");
+ static::assert(isset($info['size']), "File entries must have a size");
+ static::assert(isset($info['hash']), "File entries must have a hash");
}
else
{
- self::assert(isset($info['prefix']), "Folder entries must return a prefix");
+ static::assert(isset($info['prefix']), "Folder entries must return a prefix");
}
}
--- /dev/null
+<?php
+/**
+ * Akeeba Engine
+ *
+ * @package akeebaengine
+ * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @license GNU General Public License version 3, or later
+ */
+
+namespace Akeeba\MiniTest\Test;
+
+use Akeeba\S3\Connector;
+use Akeeba\S3\Input;
+
+class ListThousandsOfFiles extends AbstractTest
+{
+ private const PATH_PREFIX = 'massive/';
+
+ public static function setup(Connector $s3, array $options): void
+ {
+ if (defined('CREATE_2100_FILES') && CREATE_2100_FILES === false)
+ {
+ return;
+ }
+
+ $data = static::getRandomData(128);
+
+ echo "\nPopulating with 2100 files\n";
+
+ for ($i = 1; $i <= 2100; $i++)
+ {
+ if ($i % 10 === 0)
+ {
+ echo "Uploading from $i...\n";
+ }
+
+ $uri = sprintf('%stest_%04u.dat', static::PATH_PREFIX, $i);
+ $input = Input::createFromData($data);
+ $s3->putObject($input, $options['bucket'], $uri);
+ }
+ }
+
+ public static function testGetAll(Connector $s3, array $options): bool
+ {
+ $listing = $s3->getBucket($options['bucket'], static::PATH_PREFIX);
+
+ static::assert(is_array($listing), "The files listing must be an array");
+ static::assert(count($listing) === 2100, "I am expecting to see 2100 files");
+
+ for ($i = 1; $i <= 2100; $i++)
+ {
+ $key = sprintf('%stest_%04u.dat', static::PATH_PREFIX, $i);
+
+ static::assert(array_key_exists($key, $listing), sprintf('Results should list object %s', $key));
+ }
+
+ return true;
+ }
+
+ public static function testGetHundred(Connector $s3, array $options): bool
+ {
+ $listing = $s3->getBucket($options['bucket'], static::PATH_PREFIX, null, 100);
+
+ static::assert(is_array($listing), "The files listing must be an array");
+ static::assert(count($listing) === 100, "I am expecting to see 100 files");
+
+ for ($i = 1; $i <= 100; $i++)
+ {
+ $key = sprintf('%stest_%04u.dat', static::PATH_PREFIX, $i);
+
+ static::assert(array_key_exists($key, $listing), sprintf('Results should list object %s', $key));
+ }
+
+ return true;
+ }
+
+ public static function testGetElevenHundred(Connector $s3, array $options): bool
+ {
+ $listing = $s3->getBucket($options['bucket'], static::PATH_PREFIX, null, 1100);
+
+ static::assert(is_array($listing), "The files listing must be an array");
+ static::assert(count($listing) === 1100, "I am expecting to see 1100 files");
+
+ for ($i = 1; $i <= 1100; $i++)
+ {
+ $key = sprintf('%stest_%04u.dat', static::PATH_PREFIX, $i);
+
+ static::assert(array_key_exists($key, $listing), sprintf('Results should list object %s', $key));
+ }
+
+ return true;
+ }
+
+ public static function testGetLastHundred(Connector $s3, array $options): bool
+ {
+ $listing = $s3->getBucket($options['bucket'], static::PATH_PREFIX . 'test_20', null);
+
+ static::assert(is_array($listing), "The files listing must be an array");
+ static::assert(count($listing) === 100, "I am expecting to see 100 files");
+
+ for ($i = 2000; $i <= 2099; $i++)
+ {
+ $key = sprintf('%stest_%04u.dat', static::PATH_PREFIX, $i);
+
+ static::assert(array_key_exists($key, $listing), sprintf('Results should list object %s', $key));
+ }
+
+ return true;
+ }
+
+}
\ No newline at end of file
<?php
-
+/**
+ * Akeeba Engine
+ *
+ * @package akeebaengine
+ * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @license GNU General Public License version 3, or later
+ */
namespace Akeeba\MiniTest\Test;
-use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
+use Akeeba\S3\Connector;
class Multipart extends BigFiles
{
public static function setup(Connector $s3, array $options): void
{
- self::$multipart = true;
+ static::$multipart = true;
parent::setup($s3, $options);
}
$result = parent::upload5MBString($s3, $options);
$expectedChunks = 1;
- self::assert(self::$numberOfChunks === $expectedChunks, sprintf("Expected %s chunks, upload complete in %s chunks", $expectedChunks, self::$numberOfChunks));
+ static::assert(static::$numberOfChunks === $expectedChunks, sprintf("Expected %s chunks, upload complete in %s chunks", $expectedChunks, static::$numberOfChunks));
return $result;
}
$result = parent::upload6MBString($s3, $options);
$expectedChunks = 2;
- self::assert(self::$numberOfChunks === $expectedChunks, sprintf("Expected %s chunks, upload complete in %s chunks", $expectedChunks, self::$numberOfChunks));
+ static::assert(static::$numberOfChunks === $expectedChunks, sprintf("Expected %s chunks, upload complete in %s chunks", $expectedChunks, static::$numberOfChunks));
return $result;
}
$result = parent::upload10MBString($s3, $options);
$expectedChunks = 2;
- self::assert(self::$numberOfChunks === $expectedChunks, sprintf("Expected %s chunks, upload complete in %s chunks", $expectedChunks, self::$numberOfChunks));
+ static::assert(static::$numberOfChunks === $expectedChunks, sprintf("Expected %s chunks, upload complete in %s chunks", $expectedChunks, static::$numberOfChunks));
return $result;
}
$result = parent::upload11MBString($s3, $options);
$expectedChunks = 3;
- self::assert(self::$numberOfChunks === $expectedChunks, sprintf("Expected %s chunks, upload complete in %s chunks", $expectedChunks, self::$numberOfChunks));
+ static::assert(static::$numberOfChunks === $expectedChunks, sprintf("Expected %s chunks, upload complete in %s chunks", $expectedChunks, static::$numberOfChunks));
return $result;
}
$result = parent::upload5MBFile($s3, $options);
$expectedChunks = 1;
- self::assert(self::$numberOfChunks === $expectedChunks, sprintf("Expected %s chunks, upload complete in %s chunks", $expectedChunks, self::$numberOfChunks));
+ static::assert(static::$numberOfChunks === $expectedChunks, sprintf("Expected %s chunks, upload complete in %s chunks", $expectedChunks, static::$numberOfChunks));
return $result;
}
$result = parent::upload6MBFile($s3, $options);
$expectedChunks = 2;
- self::assert(self::$numberOfChunks === $expectedChunks, sprintf("Expected %s chunks, upload complete in %s chunks", $expectedChunks, self::$numberOfChunks));
+ static::assert(static::$numberOfChunks === $expectedChunks, sprintf("Expected %s chunks, upload complete in %s chunks", $expectedChunks, static::$numberOfChunks));
return $result;
}
$result = parent::upload10MBFile($s3, $options);
$expectedChunks = 2;
- self::assert(self::$numberOfChunks === $expectedChunks, sprintf("Expected %s chunks, upload complete in %s chunks", $expectedChunks, self::$numberOfChunks));
+ static::assert(static::$numberOfChunks === $expectedChunks, sprintf("Expected %s chunks, upload complete in %s chunks", $expectedChunks, static::$numberOfChunks));
return $result;
}
$result = parent::upload11MBFile($s3, $options);
$expectedChunks = 3;
- self::assert(self::$numberOfChunks === $expectedChunks, sprintf("Expected %s chunks, upload complete in %s chunks", $expectedChunks, self::$numberOfChunks));
+ static::assert(static::$numberOfChunks === $expectedChunks, sprintf("Expected %s chunks, upload complete in %s chunks", $expectedChunks, static::$numberOfChunks));
return $result;
}
* 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\MiniTest\Test;
-use Akeeba\Engine\Postproc\Connector\S3v4\Acl;
-use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
-use Akeeba\Engine\Postproc\Connector\S3v4\Input;
+use Akeeba\S3\Acl;
+use Akeeba\S3\Connector;
+use Akeeba\S3\Input;
use RuntimeException;
class SignedURLs extends AbstractTest
{
public static function signedURLPublicObject(Connector $s3, array $options): bool
{
- return self::signedURL($s3, $options, Acl::ACL_PUBLIC_READ);
+ return static::signedURL($s3, $options, Acl::ACL_PUBLIC_READ);
}
public static function signedURLPrivateObject(Connector $s3, array $options): bool
{
- return self::signedURL($s3, $options, Acl::ACL_PRIVATE);
+ return static::signedURL($s3, $options, Acl::ACL_PRIVATE);
}
private static function signedURL(Connector $s3, array $options, string $aclPrivilege): bool
{
- $tempData = self::getRandomData(AbstractTest::TEN_KB);
+ $tempData = static::getRandomData(AbstractTest::TEN_KB);
$input = Input::createFromData($tempData);
$uri = 'test.' . md5(microtime(false)) . '.dat';
throw new RuntimeException("Failed to download from signed URL ‘{$downloadURL}′");
}
- self::assert(self::areStringsEqual($tempData, $downloadedData), "Wrong data received from signed URL ‘{$downloadURL}′");
+ static::assert(static::areStringsEqual($tempData, $downloadedData), "Wrong data received from signed URL ‘{$downloadURL}′");
return true;
}
--- /dev/null
+<?php
+/**
+ * Akeeba Engine
+ *
+ * @package akeebaengine
+ * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @license GNU General Public License version 3, or later
+ */
+
+namespace Akeeba\MiniTest\Test;
+
+use Akeeba\S3\Connector;
+use Akeeba\S3\Input;
+
+/**
+ * Upload, download and delete small files (under 1MB) using a string source
+ *
+ * @package Akeeba\MiniTest\Test
+ */
+class SingleSmallFile extends AbstractTest
+{
+ public static function upload(Connector $s3, array $options): bool
+ {
+ $uri = 'test.txt';
+ $sourceData = <<< TEXT
+This is a small text file.
+TEXT;
+
+
+ // Upload the data. Throws exception if it fails.
+ $bucket = $options['bucket'];
+ $input = Input::createFromData($sourceData);
+
+ $s3->putObject($input, $bucket, $uri);
+
+ $downloadedData = $s3->getObject($bucket, $uri);
+ $result = static::areStringsEqual($sourceData, $downloadedData);
+
+ $s3->deleteObject($bucket, $uri);
+
+ return $result ?? true;
+ }
+}
\ No newline at end of file
* 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\MiniTest\Test;
-use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
-use Akeeba\Engine\Postproc\Connector\S3v4\Input;
+use Akeeba\S3\Connector;
+use Akeeba\S3\Input;
/**
* Upload, download and delete small files (under 1MB) using a file source
public static function upload10KbRoot(Connector $s3, array $options): bool
{
- return self::upload($s3, $options, AbstractTest::TEN_KB, 'root_10kb.dat');
+ return static::upload($s3, $options, AbstractTest::TEN_KB, 'root_10kb.dat');
}
public static function upload10KbRootGreek(Connector $s3, array $options): bool
{
- return self::upload($s3, $options, AbstractTest::TEN_KB, 'δοκιμή_10kb.dat');
+ return static::upload($s3, $options, AbstractTest::TEN_KB, 'δοκιμή_10kb.dat');
}
public static function upload10KbFolderGreek(Connector $s3, array $options): bool
{
- return self::upload($s3, $options, AbstractTest::TEN_KB, 'ο_φάκελός_μου/δοκιμή_10kb.dat');
+ return static::upload($s3, $options, AbstractTest::TEN_KB, 'ο_φάκελός_μου/δοκιμή_10kb.dat');
}
public static function upload600KbRoot(Connector $s3, array $options): bool
{
- return self::upload($s3, $options, AbstractTest::SIX_HUNDRED_KB, 'root_600kb.dat');
+ return static::upload($s3, $options, AbstractTest::SIX_HUNDRED_KB, 'root_600kb.dat');
}
public static function upload10KbFolder(Connector $s3, array $options): bool
{
- return self::upload($s3, $options, AbstractTest::TEN_KB, 'my_folder/10kb.dat');
+ return static::upload($s3, $options, AbstractTest::TEN_KB, 'my_folder/10kb.dat');
}
public static function upload600KbFolder(Connector $s3, array $options): bool
{
- return self::upload($s3, $options, AbstractTest::SIX_HUNDRED_KB, 'my_folder/600kb.dat');
+ return static::upload($s3, $options, AbstractTest::SIX_HUNDRED_KB, 'my_folder/600kb.dat');
}
protected static function upload(Connector $s3, array $options, int $size, string $uri): bool
$uri = substr($uri, 0, $dotPos) . '.' . md5(microtime(false)) . substr($uri, $dotPos);
// Create a file with random data
- $sourceFile = self::createFile($size);
+ $sourceFile = static::createFile($size);
// Upload the file. Throws exception if it fails.
$bucket = $options['bucket'];
$result = true;
// Should I download the file and compare its contents?
- if (self::$downloadAfter)
+ if (static::$downloadAfter)
{
// Donwload the data. Throws exception if it fails.
- $downloadedFile = tempnam(self::getTempFolder(), 'as3');
+ $downloadedFile = tempnam(static::getTempFolder(), 'as3');
$s3->getObject($bucket, $uri, $downloadedFile);
// Compare the file contents.
- $result = self::areFilesEqual($sourceFile, $downloadedFile);
+ $result = static::areFilesEqual($sourceFile, $downloadedFile);
}
// Remove the local files
@unlink($downloadedFile);
// Should I delete the remotely stored file?
- if (self::$deleteRemote)
+ if (static::$deleteRemote)
{
// Delete the remote file. Throws exception if it fails.
$s3->deleteObject($bucket, $uri);
* 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\MiniTest\Test;
-use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
+use Akeeba\S3\Connector;
/**
* Upload and download small files (under 1MB) using a file source
{
public static function setup(Connector $s3, array $options): void
{
- self::$deleteRemote = false;
+ static::$deleteRemote = false;
parent::setup($s3, $options);
}
* 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\MiniTest\Test;
-use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
+use Akeeba\S3\Connector;
/**
* Upload small files (under 1MB) using a file source
{
public static function setup(Connector $s3, array $options): void
{
- self::$deleteRemote = false;
- self::$downloadAfter = false;
+ static::$deleteRemote = false;
+ static::$downloadAfter = false;
parent::setup($s3, $options);
}
* 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\MiniTest\Test;
-
-use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
-use Akeeba\Engine\Postproc\Connector\S3v4\Input;
+use Akeeba\S3\Connector;
+use Akeeba\S3\Input;
/**
* Upload, download and delete small files (under 1MB) using a string source
$uri = substr($uri, 0, $dotPos) . '.' . md5(microtime(false)) . substr($uri, $dotPos);
// Create some random data to upload
- $sourceData = self::getRandomData($size);
+ $sourceData = static::getRandomData($size);
// Upload the data. Throws exception if it fails.
$bucket = $options['bucket'];
$result = true;
// Should I download the file and compare its contents with my random data?
- if (self::$downloadAfter)
+ if (static::$downloadAfter)
{
$downloadedData = $s3->getObject($bucket, $uri);
- $result = self::areStringsEqual($sourceData, $downloadedData);
+ $result = static::areStringsEqual($sourceData, $downloadedData);
}
// Should I delete the remotely stored file?
- if (self::$deleteRemote)
+ if (static::$deleteRemote)
{
// Delete the remote file. Throws exception if it fails.
$s3->deleteObject($bucket, $uri);
* 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\MiniTest\Test;
-use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
+use Akeeba\S3\Connector;
/**
* Upload and download small files (under 1MB) using a string source
{
public static function setup(Connector $s3, array $options): void
{
- self:: $deleteRemote = false;
+ static:: $deleteRemote = false;
parent::setup($s3, $options);
}
* 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\MiniTest\Test;
-use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
+use Akeeba\S3\Connector;
/**
* Upload small files (under 1MB) using a string source
{
public static function setup(Connector $s3, array $options): void
{
- self::$deleteRemote = false;
- self::$downloadAfter = false;
+ static::$deleteRemote = false;
+ static::$downloadAfter = false;
parent::setup($s3, $options);
}
--- /dev/null
+<?php
+/**
+ * Akeeba Engine
+ *
+ * @package akeebaengine
+ * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @license GNU General Public License version 3, or later
+ */
+
+namespace Akeeba\MiniTest\Test;
+
+
+use Akeeba\S3\Connector;
+use Akeeba\S3\Input;
+
+/**
+ * Upload, download and delete small XML files (under 1MB) using a string source
+ *
+ * @package Akeeba\MiniTest\Test
+ */
+class SmallInlineXMLFiles extends SmallFiles
+{
+ public static function upload10KbRoot(Connector $s3, array $options): bool
+ {
+ return static::upload($s3, $options, AbstractTest::TEN_KB, 'root_10kb.xml');
+ }
+
+ public static function upload10KbRootGreek(Connector $s3, array $options): bool
+ {
+ return static::upload($s3, $options, AbstractTest::TEN_KB, 'δοκιμή_10kb.xml');
+ }
+
+ public static function upload10KbFolderGreek(Connector $s3, array $options): bool
+ {
+ return static::upload($s3, $options, AbstractTest::TEN_KB, 'ο_φάκελός_μου/δοκιμή_10kb.xml');
+ }
+
+ public static function upload600KbRoot(Connector $s3, array $options): bool
+ {
+ return static::upload($s3, $options, AbstractTest::SIX_HUNDRED_KB, 'root_600kb.xml');
+ }
+
+ public static function upload10KbFolder(Connector $s3, array $options): bool
+ {
+ return static::upload($s3, $options, AbstractTest::TEN_KB, 'my_folder/10kb.xml');
+ }
+
+ public static function upload600KbFolder(Connector $s3, array $options): bool
+ {
+ return static::upload($s3, $options, AbstractTest::SIX_HUNDRED_KB, 'my_folder/600kb.xml');
+ }
+
+ protected static function upload(Connector $s3, array $options, int $size, string $uri): bool
+ {
+ // Randomize the name. Required for archive buckets where you cannot overwrite data.
+ $dotPos = strrpos($uri, '.');
+ $uri = substr($uri, 0, $dotPos) . '.' . md5(microtime(false)) . substr($uri, $dotPos);
+
+ // Create some random data to upload
+ $sourceData = static::createXMLFile($size);
+
+ // Upload the data. Throws exception if it fails.
+ $bucket = $options['bucket'];
+ $input = Input::createFromData($sourceData);
+
+ $s3->putObject($input, $bucket, $uri);
+
+ // Tentatively accept that this method succeeded.
+ $result = true;
+
+ // Should I download the file and compare its contents with my random data?
+ if (static::$downloadAfter)
+ {
+ $downloadedData = $s3->getObject($bucket, $uri);
+
+ $result = static::areStringsEqual($sourceData, $downloadedData);
+ }
+
+ // Should I delete the remotely stored file?
+ if (static::$deleteRemote)
+ {
+ // Delete the remote file. Throws exception if it fails.
+ $s3->deleteObject($bucket, $uri);
+ }
+
+ return $result;
+ }
+
+ private static function createXMLFile(int $size): string
+ {
+ $out = <<< XML
+<?xml version="1.0" encoding="utf-8" ?>
+<root>
+XML;
+
+ $chunks = floor(($size - 55) / 1024);
+
+ for ($i = 1; $i <= $chunks; $i++)
+ {
+ $randomBlock = static::genRandomData(1024 - 63);
+ $out .= <<< XML
+ <element>
+ <id>$i</id>
+ <data><![CDATA[$randomBlock]]></data>
+ </element>
+XML;
+
+ }
+
+
+ $out .= <<< XML
+</root>
+XML;
+
+ return $out;
+ }
+
+ private static function genRandomData(int $length): string
+ {
+ $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890';
+ $maxLength = strlen($chars) - 1;
+ $salt = '';
+
+ for ($i = 0; $i < $length; $i++)
+ {
+ $salt .= substr($chars, random_int(0, $maxLength), 1);
+ }
+
+ return $salt;
+ }
+}
\ No newline at end of file
* 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\MiniTest\Test;
-use Akeeba\Engine\Postproc\Connector\S3v4\Acl;
-use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
-use Akeeba\Engine\Postproc\Connector\S3v4\Input;
-use Akeeba\Engine\Postproc\Connector\S3v4\StorageClass;
+use Akeeba\S3\Acl;
+use Akeeba\S3\Connector;
+use Akeeba\S3\Input;
+use Akeeba\S3\StorageClass;
class StorageClasses extends AbstractTest
{
public static function uploadRRS(Connector $s3, array $options): bool
{
- return self::upload($s3, $options, self::TEN_KB, 'rrs_test_10kb.dat', StorageClass::REDUCED_REDUNDANCY);
+ return static::upload($s3, $options, static::TEN_KB, 'rrs_test_10kb.dat', StorageClass::REDUCED_REDUNDANCY);
}
public static function uploadIntelligentTiering(Connector $s3, array $options): bool
{
- return self::upload($s3, $options, self::TEN_KB, 'rrs_test_10kb.dat', StorageClass::INTELLIGENT_TIERING);
+ return static::upload($s3, $options, static::TEN_KB, 'rrs_test_10kb.dat', StorageClass::INTELLIGENT_TIERING);
}
protected static function upload(Connector $s3, array $options, int $size, string $uri, string $storageClass = null)
$uri = substr($uri, 0, $dotPos) . '.' . md5(microtime(false)) . substr($uri, $dotPos);
// Create some random data to upload
- $sourceData = self::getRandomData($size);
+ $sourceData = static::getRandomData($size);
// Upload the data. Throws exception if it fails.
$bucket = $options['bucket'];
$result = true;
// Should I download the file and compare its contents with my random data?
- if (self::$downloadAfter)
+ if (static::$downloadAfter)
{
$downloadedData = $s3->getObject($bucket, $uri);
- $result = self::areStringsEqual($sourceData, $downloadedData);
+ $result = static::areStringsEqual($sourceData, $downloadedData);
}
// Should I delete the remotely stored file?
- if (self::$deleteRemote)
+ if (static::$deleteRemote)
{
// Delete the remote file. Throws exception if it fails.
$s3->deleteObject($bucket, $uri);
* 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
*/
+// Custom Endpoint. The example below is for using LocalStack, see https://localstack.cloud/
+// define('DEFAULT_ENDPOINT', 'localhost.localstack.cloud:4566');
// Default Amazon S3 Access Key
define('DEFAULT_ACCESS_KEY', 'your s3 access key');
// Default Amazon S3 Secret Key
define('DEFAULT_PATH_ACCESS', false);
// Should I use SSL by default?
define('DEFAULT_SSL', true);
+// Create the 2100 test files in the bucket?
+define('CREATE_2100_FILES', true);
/**
* Tests for standard key pairs allowing us to read, write and delete
'BucketsList',
'BucketLocation',
'SmallFiles',
+ 'HeadObject',
'SmallInlineFiles',
+ 'SmallInlineXMLFiles',
'SignedURLs',
'StorageClasses',
'ListFiles',
* 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
*/
-use Akeeba\Engine\Postproc\Connector\S3v4\Configuration;
-use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
-use Akeeba\Engine\Postproc\Connector\S3v4\Input;
+use Akeeba\S3\Configuration;
+use Akeeba\S3\Connector;
+use Akeeba\S3\Input;
// Necessary for including the library
define('AKEEBAENGINE', 1);
'dualstack' => DEFAULT_DUALSTACK,
'path_access' => DEFAULT_PATH_ACCESS,
'ssl' => DEFAULT_SSL,
- 'endpoint' => null,
+ 'endpoint' => defined('DEFAULT_ENDPOINT') ? constant('DEFAULT_ENDPOINT') : null,
], $setup['configuration']);
// Extract the test classes/methods to run
// Create the S3 configuration object
$s3Configuration = new Configuration($configOptions['access'], $configOptions['secret'], $configOptions['signature'], $configOptions['region']);
- $s3Configuration->setUseDualstackUrl($configOptions['dualstack']);
- $s3Configuration->setUseLegacyPathStyle($configOptions['path_access']);
- $s3Configuration->setSSL($configOptions['ssl']);
+ $s3Configuration->setRegion($configOptions['region']);
+ $s3Configuration->setSignatureMethod($configOptions['signature']);
if (!is_null($configOptions['endpoint']))
{
$s3Configuration->setEndpoint($configOptions['endpoint']);
+ // We need to redo this because setting the endpoint may reset these options
+ $s3Configuration->setRegion($configOptions['region']);
+ $s3Configuration->setSignatureMethod($configOptions['signature']);
}
+ $s3Configuration->setUseDualstackUrl($configOptions['dualstack']);
+ $s3Configuration->setUseLegacyPathStyle($configOptions['path_access']);
+ $s3Configuration->setSSL($configOptions['ssl']);
+
// Create the connector object
$s3Connector = new Connector($s3Configuration);
* 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';
}
* 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
throw new Exception\InvalidSignatureMethod;
}
+ $this->signatureMethod = $signatureMethod;
+
// If you switch to v2 signatures we unset the region.
if ($signatureMethod == 'v2')
{
$this->setUseLegacyPathStyle(false);
}
- } else {
- if (empty($this->getRegion())) {
- $this->setRegion('us-east-1');
- }
}
-
- $this->signatureMethod = $signatureMethod;
}
/**
* 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
{
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
if (!is_resource($saveTo) && is_string($saveTo))
{
- $fp = @fopen($saveTo, 'wb');
+ $fp = @fopen($saveTo, 'w');
if ($fp === false)
{
$request->setHeader('Range', "bytes=$from-$to");
}
- $response = $request->getResponse();
+ $response = $request->getResponse(true);
if (!$response->error->isError() && (($response->code !== 200) && ($response->code !== 206)))
{
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)
+ )
);
}
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)))
+ {
+ $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)
+ )
+ );
+ }
+
+ return $response->getHeaders();
+ }
+
+
/**
* Delete an object
*
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()
+ )
);
}
}
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())
);
}
*/
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);
-
- 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);
- }
+ $internalResult = $this->internalGetBucket($bucket, $prefix, $marker, $maxKeys, $delimiter, $returnCommonPrefixes);
- $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);
+ /**
+ * @var array $objects
+ * @var ?string $nextMarker
+ */
+ extract($internalResult);
+ unset($internalResult);
- // 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))
- {
- 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))
+ // 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->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);
+ break;
}
- } 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;
}
/**
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())
);
}
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)
+ )
);
}
{
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;
+ }
}
* 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;
* 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;
* 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;
* 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;
* 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;
* 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;
* 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;
* 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;
* 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;
* 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;
* 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;
* 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;
* 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;
* 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;
* 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;
* 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;
* 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
/**
* 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
{
if (is_resource($this->fp))
{
- @fclose($this->fp);
+ try
+ {
+ @fclose($this->fp);
+ }
+ catch (\Throwable $e)
+ {
+ }
}
}
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)
{
if (is_resource($this->fp))
{
- @fclose($this->fp);
+ try
+ {
+ @fclose($this->fp);
+ }
+ catch (\Throwable $e)
+ {
+ }
}
$this->file = null;
if (is_resource($this->fp))
{
- @fclose($this->fp);
+ try
+ {
+ @fclose($this->fp);
+ }
+ catch (\Throwable $e)
+ {
+ }
}
$this->file = null;
*/
public function setSha256(?string $sha256): void
{
- $this->sha256 = strtolower($sha256);
+ $this->sha256 = is_null($sha256) ? null : strtolower($sha256);
}
/**
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:
$ext = strtolower(pathInfo($file, PATHINFO_EXTENSION));
- return isset($exts[$ext]) ? $exts[$ext] : 'application/octet-stream';
+ return $exts[$ext] ?? 'application/octet-stream';
}
/**
* 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
// 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();
*
* @return Response
*/
- public function getResponse(): Response
+ public function getResponse(bool $rawResponse = false): Response
{
$this->processParametersIntoResource();
* 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;
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);
$data = $this->input->getDataReference();
- if (strlen($data))
+ if (strlen($data ?? ''))
{
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
}
@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;
*/
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);
}
/**
* cURL header callback
*
- * @param resource $curl cURL resource
+ * @param resource $curl cURL resource
* @param string &$data Data
*
* @return int Length in bytes
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))
{
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);
$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;
}
/**
+ * 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
*
*
* 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
* 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
*
* @param string|SimpleXMLElement|null $body
*/
- public function setBody($body): void
+ public function setBody($body, bool $rawResponse = false): void
{
$this->body = null;
$this->body = $body;
- $this->finaliseBody();
+ $this->finaliseBody($rawResponse);
}
public function resetBody(): void
$this->body .= $data;
}
- public function finaliseBody(): void
+ public function finaliseBody(bool $rawResponse = false): void
{
if (!$this->hasBody())
{
$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);
)
{
$this->error = new Error(
- $this->code,
- (string) $this->body->Message
+ 500,
+ (string) $this->body->Code . ':' . (string) $this->body->Message
);
if (isset($this->body->Resource))
* 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
* 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.
*/
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);
}
* 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
}
// 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);
}
$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;
* 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;
/**
* 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';
// 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;
$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
// 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;
}
// 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);
$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 ==========
// 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.
*/
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
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;
+ }
}
* 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
/**
* 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.
--- /dev/null
+<?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;
+ }
+);
$baseDir = dirname($vendorDir);
return array(
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Acl' => $vendorDir . '/akeeba/s3/src/Acl.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Configuration' => $vendorDir . '/akeeba/s3/src/Configuration.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Connector' => $vendorDir . '/akeeba/s3/src/Connector.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Exception\\CannotDeleteFile' => $vendorDir . '/akeeba/s3/src/Exception/CannotDeleteFile.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Exception\\CannotGetBucket' => $vendorDir . '/akeeba/s3/src/Exception/CannotGetBucket.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Exception\\CannotGetFile' => $vendorDir . '/akeeba/s3/src/Exception/CannotGetFile.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Exception\\CannotListBuckets' => $vendorDir . '/akeeba/s3/src/Exception/CannotListBuckets.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Exception\\CannotOpenFileForRead' => $vendorDir . '/akeeba/s3/src/Exception/CannotOpenFileForRead.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Exception\\CannotOpenFileForWrite' => $vendorDir . '/akeeba/s3/src/Exception/CannotOpenFileForWrite.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Exception\\CannotPutFile' => $vendorDir . '/akeeba/s3/src/Exception/CannotPutFile.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Exception\\ConfigurationError' => $vendorDir . '/akeeba/s3/src/Exception/ConfigurationError.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Exception\\InvalidAccessKey' => $vendorDir . '/akeeba/s3/src/Exception/InvalidAccessKey.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Exception\\InvalidBody' => $vendorDir . '/akeeba/s3/src/Exception/InvalidBody.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Exception\\InvalidEndpoint' => $vendorDir . '/akeeba/s3/src/Exception/InvalidEndpoint.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Exception\\InvalidFilePointer' => $vendorDir . '/akeeba/s3/src/Exception/InvalidFilePointer.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Exception\\InvalidRegion' => $vendorDir . '/akeeba/s3/src/Exception/InvalidRegion.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Exception\\InvalidSecretKey' => $vendorDir . '/akeeba/s3/src/Exception/InvalidSecretKey.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Exception\\InvalidSignatureMethod' => $vendorDir . '/akeeba/s3/src/Exception/InvalidSignatureMethod.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Exception\\PropertyNotFound' => $vendorDir . '/akeeba/s3/src/Exception/PropertyNotFound.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Input' => $vendorDir . '/akeeba/s3/src/Input.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Request' => $vendorDir . '/akeeba/s3/src/Request.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Response' => $vendorDir . '/akeeba/s3/src/Response.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Response\\Error' => $vendorDir . '/akeeba/s3/src/Response/Error.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Signature' => $vendorDir . '/akeeba/s3/src/Signature.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Signature\\V2' => $vendorDir . '/akeeba/s3/src/Signature/V2.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Signature\\V4' => $vendorDir . '/akeeba/s3/src/Signature/V4.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\StorageClass' => $vendorDir . '/akeeba/s3/src/StorageClass.php',
+ 'Akeeba\\S3\\Acl' => $vendorDir . '/akeeba/s3/src/Acl.php',
+ 'Akeeba\\S3\\Configuration' => $vendorDir . '/akeeba/s3/src/Configuration.php',
+ 'Akeeba\\S3\\Connector' => $vendorDir . '/akeeba/s3/src/Connector.php',
+ 'Akeeba\\S3\\Exception\\CannotDeleteFile' => $vendorDir . '/akeeba/s3/src/Exception/CannotDeleteFile.php',
+ 'Akeeba\\S3\\Exception\\CannotGetBucket' => $vendorDir . '/akeeba/s3/src/Exception/CannotGetBucket.php',
+ 'Akeeba\\S3\\Exception\\CannotGetFile' => $vendorDir . '/akeeba/s3/src/Exception/CannotGetFile.php',
+ 'Akeeba\\S3\\Exception\\CannotListBuckets' => $vendorDir . '/akeeba/s3/src/Exception/CannotListBuckets.php',
+ 'Akeeba\\S3\\Exception\\CannotOpenFileForRead' => $vendorDir . '/akeeba/s3/src/Exception/CannotOpenFileForRead.php',
+ 'Akeeba\\S3\\Exception\\CannotOpenFileForWrite' => $vendorDir . '/akeeba/s3/src/Exception/CannotOpenFileForWrite.php',
+ 'Akeeba\\S3\\Exception\\CannotPutFile' => $vendorDir . '/akeeba/s3/src/Exception/CannotPutFile.php',
+ 'Akeeba\\S3\\Exception\\ConfigurationError' => $vendorDir . '/akeeba/s3/src/Exception/ConfigurationError.php',
+ 'Akeeba\\S3\\Exception\\InvalidAccessKey' => $vendorDir . '/akeeba/s3/src/Exception/InvalidAccessKey.php',
+ 'Akeeba\\S3\\Exception\\InvalidBody' => $vendorDir . '/akeeba/s3/src/Exception/InvalidBody.php',
+ 'Akeeba\\S3\\Exception\\InvalidEndpoint' => $vendorDir . '/akeeba/s3/src/Exception/InvalidEndpoint.php',
+ 'Akeeba\\S3\\Exception\\InvalidFilePointer' => $vendorDir . '/akeeba/s3/src/Exception/InvalidFilePointer.php',
+ 'Akeeba\\S3\\Exception\\InvalidRegion' => $vendorDir . '/akeeba/s3/src/Exception/InvalidRegion.php',
+ 'Akeeba\\S3\\Exception\\InvalidSecretKey' => $vendorDir . '/akeeba/s3/src/Exception/InvalidSecretKey.php',
+ 'Akeeba\\S3\\Exception\\InvalidSignatureMethod' => $vendorDir . '/akeeba/s3/src/Exception/InvalidSignatureMethod.php',
+ 'Akeeba\\S3\\Exception\\PropertyNotFound' => $vendorDir . '/akeeba/s3/src/Exception/PropertyNotFound.php',
+ 'Akeeba\\S3\\Input' => $vendorDir . '/akeeba/s3/src/Input.php',
+ 'Akeeba\\S3\\Request' => $vendorDir . '/akeeba/s3/src/Request.php',
+ 'Akeeba\\S3\\Response' => $vendorDir . '/akeeba/s3/src/Response.php',
+ 'Akeeba\\S3\\Response\\Error' => $vendorDir . '/akeeba/s3/src/Response/Error.php',
+ 'Akeeba\\S3\\Signature' => $vendorDir . '/akeeba/s3/src/Signature.php',
+ 'Akeeba\\S3\\Signature\\V2' => $vendorDir . '/akeeba/s3/src/Signature/V2.php',
+ 'Akeeba\\S3\\Signature\\V4' => $vendorDir . '/akeeba/s3/src/Signature/V4.php',
+ 'Akeeba\\S3\\StorageClass' => $vendorDir . '/akeeba/s3/src/StorageClass.php',
);
--- /dev/null
+<?php
+
+// autoload_files.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+ '714ccd4b330431237faf946f71c4c9a4' => $vendorDir . '/akeeba/s3/src/aliasing.php',
+);
$baseDir = dirname($vendorDir);
return array(
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\' => array($vendorDir . '/akeeba/s3/src'),
+ 'Akeeba\\S3\\' => array($vendorDir . '/akeeba/s3/src'),
);
$loader->register(true);
+ if ($useStaticLoader) {
+ $includeFiles = Composer\Autoload\ComposerStaticInitS3StorageAddon::$files;
+ } else {
+ $includeFiles = require __DIR__ . '/autoload_files.php';
+ }
+ foreach ($includeFiles as $fileIdentifier => $file) {
+ composerRequireS3StorageAddon($fileIdentifier, $file);
+ }
+
return $loader;
}
}
+
+function composerRequireS3StorageAddon($fileIdentifier, $file)
+{
+ if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
+ require $file;
+
+ $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
+ }
+}
class ComposerStaticInitS3StorageAddon
{
+ public static $files = array (
+ '714ccd4b330431237faf946f71c4c9a4' => __DIR__ . '/..' . '/akeeba/s3/src/aliasing.php',
+ );
+
public static $prefixLengthsPsr4 = array (
'A' =>
array (
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\' => 38,
+ 'Akeeba\\S3\\' => 10,
),
);
public static $prefixDirsPsr4 = array (
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\' =>
+ 'Akeeba\\S3\\' =>
array (
0 => __DIR__ . '/..' . '/akeeba/s3/src',
),
);
public static $classMap = array (
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Acl' => __DIR__ . '/..' . '/akeeba/s3/src/Acl.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Configuration' => __DIR__ . '/..' . '/akeeba/s3/src/Configuration.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Connector' => __DIR__ . '/..' . '/akeeba/s3/src/Connector.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Exception\\CannotDeleteFile' => __DIR__ . '/..' . '/akeeba/s3/src/Exception/CannotDeleteFile.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Exception\\CannotGetBucket' => __DIR__ . '/..' . '/akeeba/s3/src/Exception/CannotGetBucket.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Exception\\CannotGetFile' => __DIR__ . '/..' . '/akeeba/s3/src/Exception/CannotGetFile.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Exception\\CannotListBuckets' => __DIR__ . '/..' . '/akeeba/s3/src/Exception/CannotListBuckets.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Exception\\CannotOpenFileForRead' => __DIR__ . '/..' . '/akeeba/s3/src/Exception/CannotOpenFileForRead.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Exception\\CannotOpenFileForWrite' => __DIR__ . '/..' . '/akeeba/s3/src/Exception/CannotOpenFileForWrite.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Exception\\CannotPutFile' => __DIR__ . '/..' . '/akeeba/s3/src/Exception/CannotPutFile.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Exception\\ConfigurationError' => __DIR__ . '/..' . '/akeeba/s3/src/Exception/ConfigurationError.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Exception\\InvalidAccessKey' => __DIR__ . '/..' . '/akeeba/s3/src/Exception/InvalidAccessKey.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Exception\\InvalidBody' => __DIR__ . '/..' . '/akeeba/s3/src/Exception/InvalidBody.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Exception\\InvalidEndpoint' => __DIR__ . '/..' . '/akeeba/s3/src/Exception/InvalidEndpoint.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Exception\\InvalidFilePointer' => __DIR__ . '/..' . '/akeeba/s3/src/Exception/InvalidFilePointer.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Exception\\InvalidRegion' => __DIR__ . '/..' . '/akeeba/s3/src/Exception/InvalidRegion.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Exception\\InvalidSecretKey' => __DIR__ . '/..' . '/akeeba/s3/src/Exception/InvalidSecretKey.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Exception\\InvalidSignatureMethod' => __DIR__ . '/..' . '/akeeba/s3/src/Exception/InvalidSignatureMethod.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Exception\\PropertyNotFound' => __DIR__ . '/..' . '/akeeba/s3/src/Exception/PropertyNotFound.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Input' => __DIR__ . '/..' . '/akeeba/s3/src/Input.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Request' => __DIR__ . '/..' . '/akeeba/s3/src/Request.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Response' => __DIR__ . '/..' . '/akeeba/s3/src/Response.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Response\\Error' => __DIR__ . '/..' . '/akeeba/s3/src/Response/Error.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Signature' => __DIR__ . '/..' . '/akeeba/s3/src/Signature.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Signature\\V2' => __DIR__ . '/..' . '/akeeba/s3/src/Signature/V2.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Signature\\V4' => __DIR__ . '/..' . '/akeeba/s3/src/Signature/V4.php',
- 'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\StorageClass' => __DIR__ . '/..' . '/akeeba/s3/src/StorageClass.php',
+ 'Akeeba\\S3\\Acl' => __DIR__ . '/..' . '/akeeba/s3/src/Acl.php',
+ 'Akeeba\\S3\\Configuration' => __DIR__ . '/..' . '/akeeba/s3/src/Configuration.php',
+ 'Akeeba\\S3\\Connector' => __DIR__ . '/..' . '/akeeba/s3/src/Connector.php',
+ 'Akeeba\\S3\\Exception\\CannotDeleteFile' => __DIR__ . '/..' . '/akeeba/s3/src/Exception/CannotDeleteFile.php',
+ 'Akeeba\\S3\\Exception\\CannotGetBucket' => __DIR__ . '/..' . '/akeeba/s3/src/Exception/CannotGetBucket.php',
+ 'Akeeba\\S3\\Exception\\CannotGetFile' => __DIR__ . '/..' . '/akeeba/s3/src/Exception/CannotGetFile.php',
+ 'Akeeba\\S3\\Exception\\CannotListBuckets' => __DIR__ . '/..' . '/akeeba/s3/src/Exception/CannotListBuckets.php',
+ 'Akeeba\\S3\\Exception\\CannotOpenFileForRead' => __DIR__ . '/..' . '/akeeba/s3/src/Exception/CannotOpenFileForRead.php',
+ 'Akeeba\\S3\\Exception\\CannotOpenFileForWrite' => __DIR__ . '/..' . '/akeeba/s3/src/Exception/CannotOpenFileForWrite.php',
+ 'Akeeba\\S3\\Exception\\CannotPutFile' => __DIR__ . '/..' . '/akeeba/s3/src/Exception/CannotPutFile.php',
+ 'Akeeba\\S3\\Exception\\ConfigurationError' => __DIR__ . '/..' . '/akeeba/s3/src/Exception/ConfigurationError.php',
+ 'Akeeba\\S3\\Exception\\InvalidAccessKey' => __DIR__ . '/..' . '/akeeba/s3/src/Exception/InvalidAccessKey.php',
+ 'Akeeba\\S3\\Exception\\InvalidBody' => __DIR__ . '/..' . '/akeeba/s3/src/Exception/InvalidBody.php',
+ 'Akeeba\\S3\\Exception\\InvalidEndpoint' => __DIR__ . '/..' . '/akeeba/s3/src/Exception/InvalidEndpoint.php',
+ 'Akeeba\\S3\\Exception\\InvalidFilePointer' => __DIR__ . '/..' . '/akeeba/s3/src/Exception/InvalidFilePointer.php',
+ 'Akeeba\\S3\\Exception\\InvalidRegion' => __DIR__ . '/..' . '/akeeba/s3/src/Exception/InvalidRegion.php',
+ 'Akeeba\\S3\\Exception\\InvalidSecretKey' => __DIR__ . '/..' . '/akeeba/s3/src/Exception/InvalidSecretKey.php',
+ 'Akeeba\\S3\\Exception\\InvalidSignatureMethod' => __DIR__ . '/..' . '/akeeba/s3/src/Exception/InvalidSignatureMethod.php',
+ 'Akeeba\\S3\\Exception\\PropertyNotFound' => __DIR__ . '/..' . '/akeeba/s3/src/Exception/PropertyNotFound.php',
+ 'Akeeba\\S3\\Input' => __DIR__ . '/..' . '/akeeba/s3/src/Input.php',
+ 'Akeeba\\S3\\Request' => __DIR__ . '/..' . '/akeeba/s3/src/Request.php',
+ 'Akeeba\\S3\\Response' => __DIR__ . '/..' . '/akeeba/s3/src/Response.php',
+ 'Akeeba\\S3\\Response\\Error' => __DIR__ . '/..' . '/akeeba/s3/src/Response/Error.php',
+ 'Akeeba\\S3\\Signature' => __DIR__ . '/..' . '/akeeba/s3/src/Signature.php',
+ 'Akeeba\\S3\\Signature\\V2' => __DIR__ . '/..' . '/akeeba/s3/src/Signature/V2.php',
+ 'Akeeba\\S3\\Signature\\V4' => __DIR__ . '/..' . '/akeeba/s3/src/Signature/V4.php',
+ 'Akeeba\\S3\\StorageClass' => __DIR__ . '/..' . '/akeeba/s3/src/StorageClass.php',
);
public static function getInitializer(ClassLoader $loader)
[
{
"name": "akeeba/s3",
- "version": "2.0.0",
- "version_normalized": "2.0.0.0",
+ "version": "2.3.1",
+ "version_normalized": "2.3.1.0",
"source": {
"type": "git",
"url": "https://github.com/akeeba/s3.git",
- "reference": "01520dae1f736555e08efda0ddc1044701bd340a"
+ "reference": "7f5b3e929c93eb02ba24472560c0cbbef735aed9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/akeeba/s3/zipball/01520dae1f736555e08efda0ddc1044701bd340a",
- "reference": "01520dae1f736555e08efda0ddc1044701bd340a",
+ "url": "https://api.github.com/repos/akeeba/s3/zipball/7f5b3e929c93eb02ba24472560c0cbbef735aed9",
+ "reference": "7f5b3e929c93eb02ba24472560c0cbbef735aed9",
"shasum": ""
},
"require": {
"ext-curl": "*",
"ext-simplexml": "*",
- "php": ">=7.1.0 <8.1"
+ "php": ">=7.1.0 <8.4"
},
- "time": "2020-11-30T14:03:55+00:00",
+ "time": "2023-09-26T11:40:10+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
+ "files": [
+ "src/aliasing.php"
+ ],
"psr-4": {
- "Akeeba\\Engine\\Postproc\\Connector\\S3v4\\": "src"
+ "Akeeba\\S3\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "GPL-3.0+"
+ "GPL-3.0-or-later"
],
"authors": [
{