]> git.mxchange.org Git - friendica-addons.git/commitdiff
[s3_storage] Bump version of akeeba/s3 to version 2.3.1
authorHypolite Petovan <hypolite@mrpetovan.com>
Tue, 19 Dec 2023 02:28:16 +0000 (21:28 -0500)
committerHypolite Petovan <hypolite@mrpetovan.com>
Tue, 19 Dec 2023 02:28:16 +0000 (21:28 -0500)
- Address https://github.com/friendica/friendica/issues/12011#issuecomment-1854681792

61 files changed:
s3_storage/composer.lock
s3_storage/vendor/akeeba/s3/.gitignore
s3_storage/vendor/akeeba/s3/README.md
s3_storage/vendor/akeeba/s3/TODO.md [deleted file]
s3_storage/vendor/akeeba/s3/composer.json
s3_storage/vendor/akeeba/s3/composer.lock
s3_storage/vendor/akeeba/s3/minitest/NOTES.md [new file with mode: 0644]
s3_storage/vendor/akeeba/s3/minitest/Test/AbstractTest.php
s3_storage/vendor/akeeba/s3/minitest/Test/BigFiles.php
s3_storage/vendor/akeeba/s3/minitest/Test/BucketLocation.php
s3_storage/vendor/akeeba/s3/minitest/Test/BucketsList.php
s3_storage/vendor/akeeba/s3/minitest/Test/HeadObject.php [new file with mode: 0644]
s3_storage/vendor/akeeba/s3/minitest/Test/ListFiles.php
s3_storage/vendor/akeeba/s3/minitest/Test/ListThousandsOfFiles.php [new file with mode: 0644]
s3_storage/vendor/akeeba/s3/minitest/Test/Multipart.php
s3_storage/vendor/akeeba/s3/minitest/Test/SignedURLs.php
s3_storage/vendor/akeeba/s3/minitest/Test/SingleSmallFile.php [new file with mode: 0644]
s3_storage/vendor/akeeba/s3/minitest/Test/SmallFiles.php
s3_storage/vendor/akeeba/s3/minitest/Test/SmallFilesNoDelete.php
s3_storage/vendor/akeeba/s3/minitest/Test/SmallFilesOnlyUpload.php
s3_storage/vendor/akeeba/s3/minitest/Test/SmallInlineFiles.php
s3_storage/vendor/akeeba/s3/minitest/Test/SmallInlineFilesNoDelete.php
s3_storage/vendor/akeeba/s3/minitest/Test/SmallInlineFilesOnlyUpload.php
s3_storage/vendor/akeeba/s3/minitest/Test/SmallInlineXMLFiles.php [new file with mode: 0644]
s3_storage/vendor/akeeba/s3/minitest/Test/StorageClasses.php
s3_storage/vendor/akeeba/s3/minitest/config.dist.php
s3_storage/vendor/akeeba/s3/minitest/minitest.php
s3_storage/vendor/akeeba/s3/src/Acl.php
s3_storage/vendor/akeeba/s3/src/Configuration.php
s3_storage/vendor/akeeba/s3/src/Connector.php
s3_storage/vendor/akeeba/s3/src/Exception/CannotDeleteFile.php
s3_storage/vendor/akeeba/s3/src/Exception/CannotGetBucket.php
s3_storage/vendor/akeeba/s3/src/Exception/CannotGetFile.php
s3_storage/vendor/akeeba/s3/src/Exception/CannotListBuckets.php
s3_storage/vendor/akeeba/s3/src/Exception/CannotOpenFileForRead.php
s3_storage/vendor/akeeba/s3/src/Exception/CannotOpenFileForWrite.php
s3_storage/vendor/akeeba/s3/src/Exception/CannotPutFile.php
s3_storage/vendor/akeeba/s3/src/Exception/ConfigurationError.php
s3_storage/vendor/akeeba/s3/src/Exception/InvalidAccessKey.php
s3_storage/vendor/akeeba/s3/src/Exception/InvalidBody.php
s3_storage/vendor/akeeba/s3/src/Exception/InvalidEndpoint.php
s3_storage/vendor/akeeba/s3/src/Exception/InvalidFilePointer.php
s3_storage/vendor/akeeba/s3/src/Exception/InvalidRegion.php
s3_storage/vendor/akeeba/s3/src/Exception/InvalidSecretKey.php
s3_storage/vendor/akeeba/s3/src/Exception/InvalidSignatureMethod.php
s3_storage/vendor/akeeba/s3/src/Exception/PropertyNotFound.php
s3_storage/vendor/akeeba/s3/src/Input.php
s3_storage/vendor/akeeba/s3/src/Request.php
s3_storage/vendor/akeeba/s3/src/Response.php
s3_storage/vendor/akeeba/s3/src/Response/Error.php
s3_storage/vendor/akeeba/s3/src/Signature.php
s3_storage/vendor/akeeba/s3/src/Signature/V2.php
s3_storage/vendor/akeeba/s3/src/Signature/V4.php
s3_storage/vendor/akeeba/s3/src/StorageClass.php
s3_storage/vendor/akeeba/s3/src/aliasing.php [new file with mode: 0644]
s3_storage/vendor/composer/autoload_classmap.php
s3_storage/vendor/composer/autoload_files.php [new file with mode: 0644]
s3_storage/vendor/composer/autoload_psr4.php
s3_storage/vendor/composer/autoload_real.php
s3_storage/vendor/composer/autoload_static.php
s3_storage/vendor/composer/installed.json

index 78b10dd9a7c13d055d7cc4322e034db12c2a59f7..992e5a9e38ae79c44f248f753e30ab63fc28e748 100644 (file)
@@ -8,32 +8,35 @@
     "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": [
                 {
@@ -48,7 +51,7 @@
             "keywords": [
                 "s3"
             ],
-            "time": "2020-11-30T14:03:55+00:00"
+            "time": "2023-09-26T11:40:10+00:00"
         }
     ],
     "packages-dev": [],
index 54f46f2877ad26d3ebb67776f2cbb67bd53b171e..09e75c298d256e5cce7e02e5218a6b78d543386a 100644 (file)
@@ -1,5 +1,6 @@
 /.idea/
 /000/
-/minitest/config.php
+/minitest/config.*
+!/minitest/config.dist.php
 /minitest/tmp
 /vendor/
index d082720c26c4849c051d529e90b402fd63ff6c78..eb3c5b531e6777d1b67a9725118e4ccdddd7648c 100644 (file)
@@ -4,29 +4,53 @@ A compact, dependency-less Amazon S3 API client implementing the most commonly u
 
 ## 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 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
@@ -37,7 +61,7 @@ IP hosting the instance's metadata cache service):
 $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',
@@ -45,14 +69,14 @@ $configuration = new \Akeeba\Engine\Postproc\Connector\S3v4\Configuration(
 );
 $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. 
@@ -120,21 +144,21 @@ The last parameter (common prefixes) controls the listing of "subdirectories"
 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');
 ```
 
@@ -145,7 +169,7 @@ In all cases the entirety of the file has to be loaded in memory.
 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();
@@ -155,7 +179,7 @@ $partNumber = 0;
 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);
        
@@ -169,7 +193,7 @@ do
 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);
 
@@ -209,6 +233,23 @@ $content = $connector->getObject('mybucket', 'path/to/file.jpg', false);
 $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.
@@ -216,7 +257,7 @@ The Configuration option has optional methods which can be used to enable some u
 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'
 );
@@ -225,7 +266,7 @@ $configuration = new \Akeeba\Engine\Postproc\Connector\S3v4\Configuration(
 $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
@@ -245,7 +286,7 @@ Please note that if the S3-compatible APi uses v4 signatures you need to enter t
 ```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',
@@ -253,7 +294,7 @@ $configuration = new \Akeeba\Engine\Postproc\Connector\S3v4\Configuration(
 );
 $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.
@@ -261,14 +302,14 @@ 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
@@ -282,7 +323,7 @@ You need to do:
 $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
 
diff --git a/s3_storage/vendor/akeeba/s3/TODO.md b/s3_storage/vendor/akeeba/s3/TODO.md
deleted file mode 100644 (file)
index e19fb64..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-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
index bfb1943c0da32d8f1141ffe5a3123ba6b7c76d0a..2d3ef59f58f8a000e27ecbe7f2f007913ccbd000 100644 (file)
@@ -3,7 +3,7 @@
        "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": "*"
        },
@@ -11,7 +11,7 @@
                "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"
+               ]
        }
 }
index e7cbd746bf0053126e8b78aad5ddc2da6a148099..a7e203f932ab6de97adef2e3889faaff3ae2aa03 100644 (file)
@@ -1,19 +1,22 @@
 {
-       "_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"
 }
diff --git a/s3_storage/vendor/akeeba/s3/minitest/NOTES.md b/s3_storage/vendor/akeeba/s3/minitest/NOTES.md
new file mode 100644 (file)
index 0000000..b31f4f0
--- /dev/null
@@ -0,0 +1,62 @@
+# 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
index 8470e26076ada91997aa3ce77e00f4ce3e610e42..895259dd6497a61189e81bf86520c06e12200919 100644 (file)
@@ -3,13 +3,13 @@
  * 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
@@ -58,24 +58,24 @@ 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++)
                {
@@ -83,7 +83,7 @@ abstract class AbstractTest
 
                        if (!$reuseBlock)
                        {
-                               $blockData = self::getRandomData($blockSize);
+                               $blockData = static::getRandomData($blockSize);
                        }
                }
 
@@ -155,7 +155,7 @@ abstract class AbstractTest
                        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);
        }
 
        /**
index b7273bad7eae62467526eef6fc2b7d082c0dfb08..437d73ed16a898d34af158428f10e65376ac50a9 100644 (file)
@@ -3,15 +3,15 @@
  * 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.
@@ -51,7 +51,7 @@ class BigFiles extends AbstractTest
        /**
         * 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
         */
@@ -59,42 +59,42 @@ class BigFiles extends AbstractTest
 
        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
@@ -103,24 +103,24 @@ class BigFiles extends AbstractTest
                $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);
                }
@@ -149,7 +149,7 @@ class BigFiles extends AbstractTest
                                $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))
@@ -166,7 +166,7 @@ class BigFiles extends AbstractTest
                                $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);
@@ -176,7 +176,7 @@ class BigFiles extends AbstractTest
                $result = true;
 
                // Should I download the file and compare its contents?
-               if (self::$downloadAfter)
+               if (static::$downloadAfter)
                {
                        if ($useString)
                        {
@@ -184,16 +184,16 @@ class BigFiles extends AbstractTest
                                $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);
                        }
@@ -206,7 +206,7 @@ class BigFiles extends AbstractTest
                }
 
                // 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);
index 6ba388b46385fc790e22d5aeea55f736f4325ff8..ff04f58a5016ba54dfe38c970485fe216d92d70e 100644 (file)
@@ -3,14 +3,14 @@
  * Akeeba Engine
  *
  * @package   akeebaengine
- * @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  * @license   GNU General Public License version 3, or later
  */
 
 namespace Akeeba\MiniTest\Test;
 
 
-use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
+use Akeeba\S3\Connector;
 
 class BucketLocation extends AbstractTest
 {
@@ -18,7 +18,7 @@ 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;
        }
index 33925cd2f888edec271d8047cc63e14a85ca4150..4dab491dde3e519dd819d473422d4a33bf3cbe69 100644 (file)
@@ -3,14 +3,14 @@
  * Akeeba Engine
  *
  * @package   akeebaengine
- * @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  * @license   GNU General Public License version 3, or later
  */
 
 namespace Akeeba\MiniTest\Test;
 
 
-use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
+use Akeeba\S3\Connector;
 use RuntimeException;
 
 class BucketsList extends AbstractTest
@@ -19,16 +19,16 @@ 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'])
                        {
@@ -43,8 +43,8 @@ class BucketsList extends AbstractTest
        {
                $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;
        }
diff --git a/s3_storage/vendor/akeeba/s3/minitest/Test/HeadObject.php b/s3_storage/vendor/akeeba/s3/minitest/Test/HeadObject.php
new file mode 100644 (file)
index 0000000..535afdb
--- /dev/null
@@ -0,0 +1,67 @@
+<?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
index 53893922282999043fc0fe3585ebd86730488e68..00b5fdbf0a5535acda262c9aae0b74ac765a6fdf 100644 (file)
@@ -3,16 +3,16 @@
  * Akeeba Engine
  *
  * @package   akeebaengine
- * @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  * @license   GNU General Public License version 3, or later
  */
 
 namespace Akeeba\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
 {
@@ -34,9 +34,9 @@ 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
@@ -52,7 +52,7 @@ class ListFiles extends AbstractTest
 
        public static function teardown(Connector $s3, array $options): void
        {
-               foreach (self::$paths as $uri)
+               foreach (static::$paths as $uri)
                {
                        try
                        {
@@ -69,29 +69,29 @@ class ListFiles extends AbstractTest
        {
                $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;
@@ -101,37 +101,37 @@ class ListFiles extends AbstractTest
        {
                $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;
@@ -141,30 +141,30 @@ class ListFiles extends AbstractTest
        {
                $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;
@@ -174,41 +174,41 @@ class ListFiles extends AbstractTest
        {
                $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;
@@ -224,42 +224,42 @@ class ListFiles extends AbstractTest
                 */
                $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;
@@ -269,37 +269,37 @@ class ListFiles extends AbstractTest
        {
                $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");
                        }
                }
 
diff --git a/s3_storage/vendor/akeeba/s3/minitest/Test/ListThousandsOfFiles.php b/s3_storage/vendor/akeeba/s3/minitest/Test/ListThousandsOfFiles.php
new file mode 100644 (file)
index 0000000..8146d4c
--- /dev/null
@@ -0,0 +1,111 @@
+<?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
index e4c7c28467c1022ff4a7713ba5989ccb4d4bcb13..df097e96340032a758414bb384e6c745d2b38aeb 100644 (file)
@@ -1,16 +1,22 @@
 <?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);
        }
@@ -20,7 +26,7 @@ class Multipart extends BigFiles
                $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;
        }
@@ -30,7 +36,7 @@ class Multipart extends BigFiles
                $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;
        }
@@ -40,7 +46,7 @@ class Multipart extends BigFiles
                $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;
        }
@@ -50,7 +56,7 @@ class Multipart extends BigFiles
                $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;
        }
@@ -60,7 +66,7 @@ class Multipart extends BigFiles
                $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;
        }
@@ -70,7 +76,7 @@ class Multipart extends BigFiles
                $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;
        }
@@ -80,7 +86,7 @@ class Multipart extends BigFiles
                $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;
        }
@@ -90,7 +96,7 @@ class Multipart extends BigFiles
                $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;
        }
index 8a8f2b0b2af61fcf05618f3648e0b941d1738657..a82d4381b33d5038ed66ca9ae43eba2c86555481 100644 (file)
@@ -3,33 +3,33 @@
  * 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';
 
@@ -52,7 +52,7 @@ class SignedURLs extends AbstractTest
                        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;
        }
diff --git a/s3_storage/vendor/akeeba/s3/minitest/Test/SingleSmallFile.php b/s3_storage/vendor/akeeba/s3/minitest/Test/SingleSmallFile.php
new file mode 100644 (file)
index 0000000..9b83309
--- /dev/null
@@ -0,0 +1,43 @@
+<?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
index 52f49e587230c40c21c6d84a10caedbc500a0f55..82b2027f6ca384a20cba4ddf6097c34b849fb72c 100644 (file)
@@ -3,15 +3,15 @@
  * 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
@@ -36,32 +36,32 @@ class SmallFiles extends AbstractTest
 
        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
@@ -71,7 +71,7 @@ class SmallFiles extends AbstractTest
                $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'];
@@ -83,14 +83,14 @@ class SmallFiles extends AbstractTest
                $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
@@ -98,7 +98,7 @@ class SmallFiles extends AbstractTest
                @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);
index 7ef9cf5b1ac00c7b80bc0971d5ad884f8234534a..bdd3193b595b391b9d0a9cdc4e1846e115b10aaa 100644 (file)
@@ -3,14 +3,14 @@
  * Akeeba Engine
  *
  * @package   akeebaengine
- * @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  * @license   GNU General Public License version 3, or later
  */
 
 namespace Akeeba\MiniTest\Test;
 
 
-use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
+use Akeeba\S3\Connector;
 
 /**
  * Upload and download small files (under 1MB) using a file source
@@ -21,7 +21,7 @@ class SmallFilesNoDelete extends SmallFiles
 {
        public static function setup(Connector $s3, array $options): void
        {
-               self::$deleteRemote = false;
+               static::$deleteRemote = false;
 
                parent::setup($s3, $options);
        }
index 1b6cdca74d1c2735b136f590bb4856dfa458735b..e1aacafd1a098de8d1069b45bddfddfe228eda27 100644 (file)
@@ -3,14 +3,14 @@
  * Akeeba Engine
  *
  * @package   akeebaengine
- * @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  * @license   GNU General Public License version 3, or later
  */
 
 namespace Akeeba\MiniTest\Test;
 
 
-use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
+use Akeeba\S3\Connector;
 
 /**
  * Upload small files (under 1MB) using a file source
@@ -21,8 +21,8 @@ class SmallFilesOnlyUpload extends SmallFiles
 {
        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);
        }
index 5cb942e751bb7da7e089a7db50aca0f71933b77b..d648d1062791bea0709411050499e0d72a1b1b3e 100644 (file)
@@ -3,15 +3,14 @@
  * Akeeba Engine
  *
  * @package   akeebaengine
- * @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  * @license   GNU General Public License version 3, or later
  */
 
 namespace Akeeba\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
@@ -27,7 +26,7 @@ class SmallInlineFiles extends SmallFiles
                $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'];
@@ -39,15 +38,15 @@ class SmallInlineFiles extends SmallFiles
                $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);
index 8ba99355d1e544dec7925a875ddefbe3d939131f..d7e0d73ee6c15c711dfcb60d35fdaafd5d3c19aa 100644 (file)
@@ -3,14 +3,14 @@
  * Akeeba Engine
  *
  * @package   akeebaengine
- * @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  * @license   GNU General Public License version 3, or later
  */
 
 namespace Akeeba\MiniTest\Test;
 
 
-use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
+use Akeeba\S3\Connector;
 
 /**
  * Upload and download small files (under 1MB) using a string source
@@ -21,7 +21,7 @@ class SmallInlineFilesNoDelete extends SmallInlineFiles
 {
        public static function setup(Connector $s3, array $options): void
        {
-               self:: $deleteRemote = false;
+               static:: $deleteRemote = false;
 
                parent::setup($s3, $options);
        }
index e41bf5ec16bafa55ee0c804e0110074690004674..890ecccd7b7b05fed4631fa577e63451c1a52c23 100644 (file)
@@ -3,14 +3,14 @@
  * Akeeba Engine
  *
  * @package   akeebaengine
- * @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  * @license   GNU General Public License version 3, or later
  */
 
 namespace Akeeba\MiniTest\Test;
 
 
-use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
+use Akeeba\S3\Connector;
 
 /**
  * Upload small files (under 1MB) using a string source
@@ -21,8 +21,8 @@ class SmallInlineFilesOnlyUpload extends SmallInlineFiles
 {
        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);
        }
diff --git a/s3_storage/vendor/akeeba/s3/minitest/Test/SmallInlineXMLFiles.php b/s3_storage/vendor/akeeba/s3/minitest/Test/SmallInlineXMLFiles.php
new file mode 100644 (file)
index 0000000..5ab5489
--- /dev/null
@@ -0,0 +1,131 @@
+<?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
index 9e57ba7a7787626f84a3a613c0873a360486c600..42adce423aebd9c53900f70c3d80b8f5449475a4 100644 (file)
@@ -3,17 +3,17 @@
  * 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
 {
@@ -23,12 +23,12 @@ 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)
@@ -38,7 +38,7 @@ class StorageClasses extends AbstractTest
                $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'];
@@ -54,15 +54,15 @@ class StorageClasses extends AbstractTest
                $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);
index 063934febd70846e0189847fb2336b708949690a..b9124065ffb577e97d00bf34af159bf001040fef 100644 (file)
@@ -3,10 +3,12 @@
  * 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
@@ -23,6 +25,8 @@ define('DEFAULT_DUALSTACK', false);
 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
@@ -33,7 +37,9 @@ $standardTests = [
        'BucketsList',
        'BucketLocation',
        'SmallFiles',
+       'HeadObject',
        'SmallInlineFiles',
+       'SmallInlineXMLFiles',
        'SignedURLs',
        'StorageClasses',
        'ListFiles',
index e9490321abd163c2238856e616b58c852d49dbfd..21e9dce1e3de962851445d16947be296ca40473c 100644 (file)
@@ -3,13 +3,13 @@
  * 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);
@@ -167,7 +167,7 @@ foreach ($testConfigurations as $description => $setup)
                '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
@@ -185,15 +185,21 @@ foreach ($testConfigurations as $description => $setup)
 
        // 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);
 
index 36952cba3b2b175d732c4d5d55803831d240a38d..3a58f5e8cbba0851fa314bac2ae32b1bfbcf36a4 100644 (file)
@@ -3,29 +3,29 @@
  * Akeeba Engine
  *
  * @package   akeebaengine
- * @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  * @license   GNU General Public License version 3, or later
  */
 
-namespace Akeeba\Engine\Postproc\Connector\S3v4;
+namespace Akeeba\S3;
 
 // Protection against direct access
-defined('AKEEBAENGINE') or die();
+defined('AKEEBAENGINE') || die();
 
 /**
  * Shortcuts to often used access control privileges
  */
 class Acl
 {
-       const ACL_PRIVATE = 'private';
+       public const ACL_PRIVATE = 'private';
 
-       const ACL_PUBLIC_READ = 'public-read';
+       public const ACL_PUBLIC_READ = 'public-read';
 
-       const ACL_PUBLIC_READ_WRITE = 'public-read-write';
+       public const ACL_PUBLIC_READ_WRITE = 'public-read-write';
 
-       const ACL_AUTHENTICATED_READ = 'authenticated-read';
+       public const ACL_AUTHENTICATED_READ = 'authenticated-read';
 
-       const ACL_BUCKET_OWNER_READ = 'bucket-owner-read';
+       public const ACL_BUCKET_OWNER_READ = 'bucket-owner-read';
 
-       const ACL_BUCKET_OWNER_FULL_CONTROL = 'bucket-owner-full-control';
+       public const ACL_BUCKET_OWNER_FULL_CONTROL = 'bucket-owner-full-control';
 }
index 13540dd0e1b6112ba05f55ede9472c0ce35e5868..ca5fa19e21658838abf269bd943780d2aea927d2 100644 (file)
@@ -3,14 +3,14 @@
  * Akeeba Engine
  *
  * @package   akeebaengine
- * @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  * @license   GNU General Public License version 3, or later
  */
 
-namespace Akeeba\Engine\Postproc\Connector\S3v4;
+namespace Akeeba\S3;
 
 // Protection against direct access
-defined('AKEEBAENGINE') or die();
+defined('AKEEBAENGINE') || die();
 
 /**
  * Holds the Amazon S3 confiugration credentials
@@ -199,6 +199,8 @@ class Configuration
                        throw new Exception\InvalidSignatureMethod;
                }
 
+               $this->signatureMethod = $signatureMethod;
+
                // If you switch to v2 signatures we unset the region.
                if ($signatureMethod == 'v2')
                {
@@ -214,13 +216,7 @@ class Configuration
                                $this->setUseLegacyPathStyle(false);
                        }
 
-               } else {
-                       if (empty($this->getRegion())) {
-                               $this->setRegion('us-east-1');
-                       }
                }
-
-               $this->signatureMethod = $signatureMethod;
        }
 
        /**
index 4bf52caae99b3f0b2685d03130ec9676706d7d74..54fdc3cc3653e6ccc85fedb9e651006cf4f4d859 100644 (file)
@@ -3,22 +3,22 @@
  * Akeeba Engine
  *
  * @package   akeebaengine
- * @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  * @license   GNU General Public License version 3, or later
  */
 
-namespace Akeeba\Engine\Postproc\Connector\S3v4;
+namespace Akeeba\S3;
 
 // Protection against direct access
-use Akeeba\Engine\Postproc\Connector\S3v4\Exception\CannotDeleteFile;
-use Akeeba\Engine\Postproc\Connector\S3v4\Exception\CannotGetBucket;
-use Akeeba\Engine\Postproc\Connector\S3v4\Exception\CannotGetFile;
-use Akeeba\Engine\Postproc\Connector\S3v4\Exception\CannotListBuckets;
-use Akeeba\Engine\Postproc\Connector\S3v4\Exception\CannotOpenFileForWrite;
-use Akeeba\Engine\Postproc\Connector\S3v4\Exception\CannotPutFile;
-use Akeeba\Engine\Postproc\Connector\S3v4\Response\Error;
+use Akeeba\S3\Exception\CannotDeleteFile;
+use Akeeba\S3\Exception\CannotGetBucket;
+use Akeeba\S3\Exception\CannotGetFile;
+use Akeeba\S3\Exception\CannotListBuckets;
+use Akeeba\S3\Exception\CannotOpenFileForWrite;
+use Akeeba\S3\Exception\CannotPutFile;
+use Akeeba\S3\Response\Error;
 
-defined('AKEEBAENGINE') or die();
+defined('AKEEBAENGINE') || die();
 
 class Connector
 {
@@ -81,7 +81,10 @@ class Connector
 
                if (($input->getSize() <= 0) || (($input->getInputType() == Input::INPUT_DATA) && (!strlen($input->getDataReference()))))
                {
-                       throw new CannotPutFile('Missing input parameters', 0);
+                       if (substr($uri, -1) !== '/')
+                       {
+                               throw new CannotPutFile('Missing input parameters', 0);
+                       }
                }
 
                // We need to post with Content-Length and Content-Type, MD5 is optional
@@ -169,7 +172,7 @@ class Connector
 
                if (!is_resource($saveTo) && is_string($saveTo))
                {
-                       $fp = @fopen($saveTo, 'wb');
+                       $fp = @fopen($saveTo, 'w');
 
                        if ($fp === false)
                        {
@@ -193,7 +196,7 @@ class Connector
                        $request->setHeader('Range', "bytes=$from-$to");
                }
 
-               $response = $request->getResponse();
+               $response = $request->getResponse(true);
 
                if (!$response->error->isError() && (($response->code !== 200) && ($response->code !== 206)))
                {
@@ -206,9 +209,14 @@ class Connector
                if ($response->error->isError())
                {
                        throw new CannotGetFile(
-                               sprintf(__METHOD__ . "({$bucket}, {$uri}): [%s] %s\n\nDebug info:\n%s",
-                                       $response->error->getCode(), $response->error->getMessage(), print_r($response->body, true)),
-                               $response->error->getCode()
+                               sprintf(
+                                       __METHOD__ . "({%s}, {%s}): [%s] %s\n\nDebug info:\n%s",
+                                       $bucket,
+                                       $uri,
+                                       $response->error->getCode(),
+                                       $response->error->getMessage(),
+                                       print_r($response->body, true)
+                               )
                        );
                }
 
@@ -220,6 +228,49 @@ class Connector
                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
         *
@@ -244,9 +295,13 @@ class Connector
                if ($response->error->isError())
                {
                        throw new CannotDeleteFile(
-                               sprintf(__METHOD__ . "({$bucket}, {$uri}): [%s] %s",
-                                       $response->error->getCode(), $response->error->getMessage()),
-                               $response->error->getCode()
+                               sprintf(
+                                       __METHOD__ . "({%s}, {%s}): [%s] %s",
+                                       $bucket,
+                                       $uri,
+                                       $response->error->getCode(),
+                                       $response->error->getMessage()
+                               )
                        );
                }
        }
@@ -358,8 +413,7 @@ class Connector
                if ($response->error->isError())
                {
                        throw new CannotGetBucket(
-                               sprintf(__METHOD__ . "(): [%s] %s", $response->error->getCode(), $response->error->getMessage()),
-                               $response->error->getCode()
+                               sprintf(__METHOD__ . "(): [%s] %s", $response->error->getCode(), $response->error->getMessage())
                        );
                }
 
@@ -403,168 +457,47 @@ class Connector
         */
        public function getBucket(string $bucket, ?string $prefix = null, ?string $marker = null, ?int $maxKeys = null, string $delimiter = '/', bool $returnCommonPrefixes = false): array
        {
-               $request = new Request('GET', $bucket, '', $this->configuration);
-
-               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;
        }
 
        /**
@@ -594,8 +527,7 @@ class Connector
                if ($response->error->isError())
                {
                        throw new CannotListBuckets(
-                               sprintf(__METHOD__ . "(): [%s] %s", $response->error->getCode(), $response->error->getMessage()),
-                               $response->error->getCode()
+                               sprintf(__METHOD__ . "(): [%s] %s", $response->error->getCode(), $response->error->getMessage())
                        );
                }
 
@@ -691,7 +623,12 @@ class Connector
                if ($response->error->isError())
                {
                        throw new CannotPutFile(
-                               sprintf(__METHOD__ . "(): [%s] %s\n\nDebug info:\n%s", $response->error->getCode(), $response->error->getMessage(), print_r($response->body, true))
+                               sprintf(
+                                       __METHOD__ . "(): [%s] %s\n\nDebug info:\n%s",
+                                       $response->error->getCode(),
+                                       $response->error->getMessage(),
+                                       print_r($response->body, true)
+                               )
                        );
                }
 
@@ -958,4 +895,90 @@ class Connector
        {
                return $this->configuration;
        }
+
+       private function internalGetBucket(string $bucket, ?string $prefix = null, ?string $marker = null, ?int $maxKeys = null, string $delimiter = '/', bool $returnCommonPrefixes = false): array
+       {
+               $request = new Request('GET', $bucket, '', $this->configuration);
+
+               if (!empty($prefix))
+               {
+                       $request->setParameter('prefix', $prefix);
+               }
+
+               if (!empty($marker))
+               {
+                       $request->setParameter('marker', $marker);
+               }
+
+               if (!empty($maxKeys))
+               {
+                       $request->setParameter('max-keys', $maxKeys);
+               }
+
+               if (!empty($delimiter))
+               {
+                       $request->setParameter('delimiter', $delimiter);
+               }
+
+               $response = $request->getResponse();
+
+               if (!$response->error->isError() && $response->code !== 200)
+               {
+                       $response->error = new Error(
+                               $response->code,
+                               "Unexpected HTTP status {$response->code}"
+                       );
+               }
+
+               if ($response->error->isError())
+               {
+                       throw new CannotGetBucket(
+                               sprintf(__METHOD__ . "(): [%s] %s", $response->error->getCode(), $response->error->getMessage())
+                       );
+               }
+
+               $results = [
+                       'objects'    => [],
+                       'nextMarker' => null,
+               ];
+
+               if ($response->hasBody() && isset($response->body->Contents))
+               {
+                       foreach ($response->body->Contents as $c)
+                       {
+                               $results['objects'][(string) $c->Key] = [
+                                       'name' => (string) $c->Key,
+                                       'time' => strtotime((string) $c->LastModified),
+                                       'size' => (int) $c->Size,
+                                       'hash' => substr((string) $c->ETag, 1, -1),
+                               ];
+
+                               $results['nextMarker'] = (string) $c->Key;
+                       }
+               }
+
+               if ($returnCommonPrefixes && $response->hasBody() && isset($response->body->CommonPrefixes))
+               {
+                       foreach ($response->body->CommonPrefixes as $c)
+                       {
+                               $results['objects'][(string) $c->Prefix] = ['prefix' => (string) $c->Prefix];
+                       }
+               }
+
+               if ($response->hasBody() && isset($response->body->IsTruncated) &&
+                       ((string) $response->body->IsTruncated == 'false')
+               )
+               {
+                       $results['nextMarker'] = null;
+
+                       return $results;
+               }
+
+               if ($response->hasBody() && isset($response->body->NextMarker))
+               {
+                       $results['nextMarker'] = (string) $response->body->NextMarker;
+               }
+
+               return $results;
+       }
 }
index b98c566e5063563b755f43609ece1bcbe61447d6..05e3fa507cec2da72b80e0a7cdb573fe0cb87035 100644 (file)
@@ -3,14 +3,14 @@
  * Akeeba Engine
  *
  * @package   akeebaengine
- * @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  * @license   GNU General Public License version 3, or later
  */
 
-namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
+namespace Akeeba\S3\Exception;
 
 // Protection against direct access
-defined('AKEEBAENGINE') or die();
+defined('AKEEBAENGINE') || die();
 
 use RuntimeException;
 
index 6047a4b4bf0484871c58f94ea16b210f3b390b5b..e87c0994d1e14619c435b4da6a278b881ad42b8c 100644 (file)
@@ -3,14 +3,14 @@
  * Akeeba Engine
  *
  * @package   akeebaengine
- * @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  * @license   GNU General Public License version 3, or later
  */
 
-namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
+namespace Akeeba\S3\Exception;
 
 // Protection against direct access
-defined('AKEEBAENGINE') or die();
+defined('AKEEBAENGINE') || die();
 
 use RuntimeException;
 
index b971935330f7bcccfdc5fe1119f230f8d4c5198c..5a9d8e82318c6121014e7489b721ef7f63614763 100644 (file)
@@ -3,14 +3,14 @@
  * Akeeba Engine
  *
  * @package   akeebaengine
- * @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  * @license   GNU General Public License version 3, or later
  */
 
-namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
+namespace Akeeba\S3\Exception;
 
 // Protection against direct access
-defined('AKEEBAENGINE') or die();
+defined('AKEEBAENGINE') || die();
 
 use RuntimeException;
 
index de29bcbda27ad010f380723309ae13c4ee784d4d..7a365fbaae9c6b365d326d10ef1dc64baa6369a0 100644 (file)
@@ -3,14 +3,14 @@
  * Akeeba Engine
  *
  * @package   akeebaengine
- * @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  * @license   GNU General Public License version 3, or later
  */
 
-namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
+namespace Akeeba\S3\Exception;
 
 // Protection against direct access
-defined('AKEEBAENGINE') or die();
+defined('AKEEBAENGINE') || die();
 
 use RuntimeException;
 
index 6a206ffe8be03d18bf55ff0db8a60e1b8ac3a9f0..78aa5cd2ef9f917fea0c2375f67b2d6c2c1a5a1b 100644 (file)
@@ -3,14 +3,14 @@
  * Akeeba Engine
  *
  * @package   akeebaengine
- * @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  * @license   GNU General Public License version 3, or later
  */
 
-namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
+namespace Akeeba\S3\Exception;
 
 // Protection against direct access
-defined('AKEEBAENGINE') or die();
+defined('AKEEBAENGINE') || die();
 
 use Exception;
 use RuntimeException;
index ecec4e701e75178802adc54942b059d10f9f19a9..24f8a724b21301592865fcea236646a1b5baa504 100644 (file)
@@ -3,14 +3,14 @@
  * Akeeba Engine
  *
  * @package   akeebaengine
- * @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  * @license   GNU General Public License version 3, or later
  */
 
-namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
+namespace Akeeba\S3\Exception;
 
 // Protection against direct access
-defined('AKEEBAENGINE') or die();
+defined('AKEEBAENGINE') || die();
 
 use Exception;
 use RuntimeException;
index 9f53c4f5c24cc168f017cb038852d680102d0d2a..a7a2a47848faa7ace7d8ea0212ddaebb65dea85b 100644 (file)
@@ -3,14 +3,14 @@
  * Akeeba Engine
  *
  * @package   akeebaengine
- * @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  * @license   GNU General Public License version 3, or later
  */
 
-namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
+namespace Akeeba\S3\Exception;
 
 // Protection against direct access
-defined('AKEEBAENGINE') or die();
+defined('AKEEBAENGINE') || die();
 
 use RuntimeException;
 
index 60d64a6cb2045ee881b3b4e53a24a06d4da563c9..2699b5e71ce94a5784d34b0ac9a8d73ed6f856be 100644 (file)
@@ -3,14 +3,14 @@
  * Akeeba Engine
  *
  * @package   akeebaengine
- * @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  * @license   GNU General Public License version 3, or later
  */
 
-namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
+namespace Akeeba\S3\Exception;
 
 // Protection against direct access
-defined('AKEEBAENGINE') or die();
+defined('AKEEBAENGINE') || die();
 
 use RuntimeException;
 
index dd5616860636be98112e9cd28f631f75a0b8c724..3db444b460d17d87e3e626bce40d642ba6e53b49 100644 (file)
@@ -3,14 +3,14 @@
  * Akeeba Engine
  *
  * @package   akeebaengine
- * @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  * @license   GNU General Public License version 3, or later
  */
 
-namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
+namespace Akeeba\S3\Exception;
 
 // Protection against direct access
-defined('AKEEBAENGINE') or die();
+defined('AKEEBAENGINE') || die();
 
 use Exception;
 
index 6e1e1690423757a317d594e865cfc1ea2d956179..57b628fd8b2d5759084f74d5342dd3361e13fa84 100644 (file)
@@ -3,14 +3,14 @@
  * Akeeba Engine
  *
  * @package   akeebaengine
- * @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  * @license   GNU General Public License version 3, or later
  */
 
-namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
+namespace Akeeba\S3\Exception;
 
 // Protection against direct access
-defined('AKEEBAENGINE') or die();
+defined('AKEEBAENGINE') || die();
 
 use Exception;
 use RuntimeException;
index b35e56d0f42d7acb9fc325486b811c5681bc3d46..3c58c0354bbfea9e71fa624756dc3fafbf8ce1e2 100644 (file)
@@ -3,14 +3,14 @@
  * Akeeba Engine
  *
  * @package   akeebaengine
- * @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  * @license   GNU General Public License version 3, or later
  */
 
-namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
+namespace Akeeba\S3\Exception;
 
 // Protection against direct access
-defined('AKEEBAENGINE') or die();
+defined('AKEEBAENGINE') || die();
 
 use Exception;
 
index 084b3cd0a22a11dd1340ecf07e9eda2ba4636377..15c546cd0501fb781f7e6cf9f9377c32a13e3bfc 100644 (file)
@@ -3,14 +3,14 @@
  * Akeeba Engine
  *
  * @package   akeebaengine
- * @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  * @license   GNU General Public License version 3, or later
  */
 
-namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
+namespace Akeeba\S3\Exception;
 
 // Protection against direct access
-defined('AKEEBAENGINE') or die();
+defined('AKEEBAENGINE') || die();
 
 use Exception;
 use InvalidArgumentException;
index fe6abc0830de8e167cfdf62d667525f51bf50ec2..e21c1f67eb88ca786002fd43f267a7a4235a8a94 100644 (file)
@@ -3,14 +3,14 @@
  * Akeeba Engine
  *
  * @package   akeebaengine
- * @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  * @license   GNU General Public License version 3, or later
  */
 
-namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
+namespace Akeeba\S3\Exception;
 
 // Protection against direct access
-defined('AKEEBAENGINE') or die();
+defined('AKEEBAENGINE') || die();
 
 use Exception;
 
index 2348c28948e19308b6beeba8c15be430b558d2bc..af58e5d9e085c02874c02c1709e6658752d86adb 100644 (file)
@@ -3,14 +3,14 @@
  * Akeeba Engine
  *
  * @package   akeebaengine
- * @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  * @license   GNU General Public License version 3, or later
  */
 
-namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
+namespace Akeeba\S3\Exception;
 
 // Protection against direct access
-defined('AKEEBAENGINE') or die();
+defined('AKEEBAENGINE') || die();
 
 use Exception;
 
index 6145ed1c9a9d2a8ea9060978da66bb9cf38234f6..bd4fdedca0f183a41627eeb5ce3a3a0347e4e54f 100644 (file)
@@ -3,14 +3,14 @@
  * Akeeba Engine
  *
  * @package   akeebaengine
- * @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  * @license   GNU General Public License version 3, or later
  */
 
-namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
+namespace Akeeba\S3\Exception;
 
 // Protection against direct access
-defined('AKEEBAENGINE') or die();
+defined('AKEEBAENGINE') || die();
 
 use Exception;
 
index c397fee86ab6efb77e65f4c23326a4c915c28844..3f521532b14922c1643b7e2d18734357c6a3e858 100644 (file)
@@ -3,14 +3,14 @@
  * Akeeba Engine
  *
  * @package   akeebaengine
- * @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  * @license   GNU General Public License version 3, or later
  */
 
-namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
+namespace Akeeba\S3\Exception;
 
 // Protection against direct access
-defined('AKEEBAENGINE') or die();
+defined('AKEEBAENGINE') || die();
 
 use LogicException;
 
index 4420bdba957caa4ac54aa91cd13efd9a0971484f..5dd5743d02ae719659add99c8add43013cbc6012 100644 (file)
@@ -3,14 +3,14 @@
  * Akeeba Engine
  *
  * @package   akeebaengine
- * @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  * @license   GNU General Public License version 3, or later
  */
 
-namespace Akeeba\Engine\Postproc\Connector\S3v4;
+namespace Akeeba\S3;
 
 // Protection against direct access
-defined('AKEEBAENGINE') or die();
+defined('AKEEBAENGINE') || die();
 
 /**
  * Defines an input source for PUT/POST requests to Amazon S3
@@ -20,17 +20,17 @@ class Input
        /**
         * Input type: resource
         */
-       const INPUT_RESOURCE = 1;
+       public const INPUT_RESOURCE = 1;
 
        /**
         * Input type: file
         */
-       const INPUT_FILE = 2;
+       public const INPUT_FILE = 2;
 
        /**
         * Input type: raw data
         */
-       const INPUT_DATA = 3;
+       public const INPUT_DATA = 3;
 
        /**
         * File pointer, in case we have a resource
@@ -177,7 +177,13 @@ class Input
        {
                if (is_resource($this->fp))
                {
-                       @fclose($this->fp);
+                       try
+                       {
+                               @fclose($this->fp);
+                       }
+                       catch (\Throwable $e)
+                       {
+                       }
                }
        }
 
@@ -258,10 +264,16 @@ class Input
 
                if (is_resource($this->fp))
                {
-                       @fclose($this->fp);
+                       try
+                       {
+                               @fclose($this->fp);
+                       }
+                       catch (\Throwable $e)
+                       {
+                       }
                }
 
-               $this->fp = @fopen($file, 'rb');
+               $this->fp = @fopen($file, 'r');
 
                if ($this->fp === false)
                {
@@ -295,7 +307,13 @@ class Input
 
                if (is_resource($this->fp))
                {
-                       @fclose($this->fp);
+                       try
+                       {
+                               @fclose($this->fp);
+                       }
+                       catch (\Throwable $e)
+                       {
+                       }
                }
 
                $this->file = null;
@@ -329,7 +347,13 @@ class Input
 
                if (is_resource($this->fp))
                {
-                       @fclose($this->fp);
+                       try
+                       {
+                               @fclose($this->fp);
+                       }
+                       catch (\Throwable $e)
+                       {
+                       }
                }
 
                $this->file = null;
@@ -450,7 +474,7 @@ class Input
         */
        public function setSha256(?string $sha256): void
        {
-               $this->sha256 = strtolower($sha256);
+               $this->sha256 = is_null($sha256) ? null : strtolower($sha256);
        }
 
        /**
@@ -532,7 +556,7 @@ class Input
                switch ($this->getInputType())
                {
                        case self::INPUT_DATA:
-                               return function_exists('mb_strlen') ? mb_strlen($this->data, '8bit') : strlen($this->data);
+                               return function_exists('mb_strlen') ? mb_strlen($this->data ?? '', '8bit') : strlen($this->data ?? '');
                                break;
 
                        case self::INPUT_FILE:
@@ -635,7 +659,7 @@ class Input
 
                $ext = strtolower(pathInfo($file, PATHINFO_EXTENSION));
 
-               return isset($exts[$ext]) ? $exts[$ext] : 'application/octet-stream';
+               return $exts[$ext] ?? 'application/octet-stream';
        }
 
        /**
index 172113fccd7cf674495484af2d8dc16f2684c2de..4237b862b034a99ae480431ee31ee865d70b0b6a 100644 (file)
@@ -3,16 +3,16 @@
  * Akeeba Engine
  *
  * @package   akeebaengine
- * @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  * @license   GNU General Public License version 3, or later
  */
 
-namespace Akeeba\Engine\Postproc\Connector\S3v4;
+namespace Akeeba\S3;
 
-use Akeeba\Engine\Postproc\Connector\S3v4\Response\Error;
+use Akeeba\S3\Response\Error;
 
 // Protection against direct access
-defined('AKEEBAENGINE') or die();
+defined('AKEEBAENGINE') || die();
 
 
 class Request
@@ -142,6 +142,12 @@ class Request
                // The date must always be added as a header
                $this->headers['Date'] = gmdate('D, d M Y H:i:s O');
 
+               // S3-"compatible" services use a different date format. Because why not?
+               if (strpos($this->headers['Host'], '.amazonaws.com') === false)
+               {
+                       $this->headers['Date'] = gmdate('D, d M Y H:i:s T');
+               }
+
                // If there is a security token we need to set up the X-Amz-Security-Token header
                $token = $this->configuration->getToken();
 
@@ -367,7 +373,7 @@ class Request
         *
         * @return  Response
         */
-       public function getResponse(): Response
+       public function getResponse(bool $rawResponse = false): Response
        {
                $this->processParametersIntoResource();
 
@@ -417,8 +423,10 @@ class Request
                         * Caveat: if your bucket contains dots in the name we have to turn off host verification due to the way the
                         * S3 SSL certificates are set up.
                         */
-                       $isAmazonS3  = (substr($this->headers['Host'], -14) == '.amazonaws.com') ||
-                               substr($this->headers['Host'], -16) == 'amazonaws.com.cn';
+                       $isAmazonS3  = (substr($this->headers['Host'], -14) == '.amazonaws.com')
+                                      || substr(
+                                                 $this->headers['Host'], -16
+                                         ) == 'amazonaws.com.cn';
                        $tooManyDots = substr_count($this->headers['Host'], '.') > 4;
 
                        $verifyHost = ($isAmazonS3 && $tooManyDots) ? 0 : 2;
@@ -429,6 +437,27 @@ class Request
 
                curl_setopt($curl, CURLOPT_URL, $url);
 
+               /**
+                * Set the optional x-amz-date header for third party services.
+                *
+                * Amazon S3 proper expects to get the date from the Date header. Third party services typically implement the
+                * (wrongly) documented behaviour of using the x-amz-date header but, if it's missing, fall back to the Date
+                * header. Wasabi does not fall back; it only uses the x-amz-date header which is why we have to set it here if
+                * the request iss not made to Amazon S3 proper.
+                */
+               $this->headers['x-amz-date'] = strpos($this->headers['Host'], '.amazonaws.com') !== false
+                       ? ''
+                       : (new \DateTime($this->headers['Date']))->format('Ymd\THis\Z');
+
+               /**
+                * Remove empty headers.
+                *
+                * While Amazon S3 proper and most third party implementations have no problem with that, there a few of them
+                * (such as Synology C2) which choke on empty headers.
+                */
+               $this->headers = array_filter($this->headers);
+
+               // Get the request signature
                $signer = Signature::getSignatureObject($this, $this->configuration->getSignatureMethod());
                $signer->preProcessHeaders($this->headers, $this->amzHeaders);
 
@@ -482,7 +511,7 @@ class Request
 
                                        $data = $this->input->getDataReference();
 
-                                       if (strlen($data))
+                                       if (strlen($data ?? ''))
                                        {
                                                curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
                                        }
@@ -538,12 +567,18 @@ class Request
                @curl_close($curl);
 
                // Set the body data
-               $this->response->finaliseBody();
+               $this->response->finaliseBody($rawResponse);
 
                // Clean up file resources
                if (!is_null($this->fp) && is_resource($this->fp))
                {
-                       fclose($this->fp);
+                       try
+                       {
+                               @fclose($this->fp);
+                       }
+                       catch (\Throwable $e)
+                       {
+                       }
                }
 
                return $this->response;
@@ -560,7 +595,7 @@ class Request
         */
        protected function __responseWriteCallback($curl, string $data): int
        {
-               if (in_array($this->response->code, [200, 206]) && !is_null($this->fp) && is_resource($this->fp))
+               if (in_array($this->response->code, [0, 200, 206]) && !is_null($this->fp) && is_resource($this->fp))
                {
                        return fwrite($this->fp, $data);
                }
@@ -573,7 +608,7 @@ class Request
        /**
         * cURL header callback
         *
-        * @param   resource  $curl  cURL resource
+        * @param   resource   $curl  cURL resource
         * @param   string    &$data  Data
         *
         * @return  int  Length in bytes
@@ -592,7 +627,15 @@ class Request
                        return $strlen;
                }
 
-               [$header, $value] = explode(': ', trim($data), 2);
+               // Ignore malformed headers without a value.
+               if (strpos($data, ':') === false)
+               {
+                       return $strlen;
+               }
+
+               [$header, $value] = explode(':', trim($data), 2);
+               $header = trim($header ?? '');
+               $value  = trim($value ?? '');
 
                switch (strtolower($header))
                {
@@ -609,10 +652,12 @@ class Request
                                break;
 
                        case 'etag':
-                               $this->response->setHeader('hash', $value[0] == '"' ? substr($value, 1, -1) : $value);
+                               $this->response->setHeader('hash', trim($value, '"'));
                                break;
 
                        default:
+                               $this->response->setHeader(strtolower($header), is_numeric($value) ? (int) $value : $value);
+
                                if (preg_match('/^x-amz-meta-.*$/', $header))
                                {
                                        $this->setHeader($header, is_numeric($value) ? (int) $value : $value);
@@ -652,13 +697,12 @@ class Request
                        $query     = substr($query, 0, -1);
                        $this->uri .= $query;
 
-                       if (array_key_exists('acl', $this->parameters) ||
-                               array_key_exists('location', $this->parameters) ||
-                               array_key_exists('torrent', $this->parameters) ||
-                               array_key_exists('logging', $this->parameters) ||
-                               array_key_exists('uploads', $this->parameters) ||
-                               array_key_exists('uploadId', $this->parameters) ||
-                               array_key_exists('partNumber', $this->parameters)
+                       if (array_key_exists('acl', $this->parameters) || array_key_exists('location', $this->parameters)
+                           || array_key_exists('torrent', $this->parameters)
+                           || array_key_exists('logging', $this->parameters)
+                           || array_key_exists('uploads', $this->parameters)
+                           || array_key_exists('uploadId', $this->parameters)
+                           || array_key_exists('partNumber', $this->parameters)
                        )
                        {
                                $this->resource .= $query;
@@ -720,6 +764,8 @@ class Request
                }
 
                /**
+                * Only applies to Amazon S3 proper.
+                *
                 * When using the Amazon S3 with the v4 signature API we have to use a different hostname per region. The
                 * mapping can be found in https://docs.aws.amazon.com/general/latest/gr/s3.html#s3_region
                 *
@@ -728,25 +774,27 @@ class Request
                 *
                 * v4 signing does NOT support non-Amazon endpoints.
                 */
-
-               // Most endpoints: s3-REGION.amazonaws.com
-               $regionalEndpoint = $region . '.amazonaws.com';
-
-               // Exception: China
-               if (substr($region, 0, 3) == 'cn-')
+               if (in_array($endpoint, ['s3.amazonaws.com', 'amazonaws.com.cn']))
                {
-                       // Chinese endpoint, e.g.: s3.cn-north-1.amazonaws.com.cn
-                       $regionalEndpoint = $regionalEndpoint . '.cn';
-               }
+                       // Most endpoints: s3-REGION.amazonaws.com
+                       $regionalEndpoint = $region . '.amazonaws.com';
 
-               // If dual-stack URLs are enabled then prepend the endpoint
-               if ($configuration->getDualstackUrl())
-               {
-                       $endpoint = 's3.dualstack.' . $regionalEndpoint;
-               }
-               else
-               {
-                       $endpoint = 's3.' . $regionalEndpoint;
+                       // Exception: China
+                       if (substr($region, 0, 3) == 'cn-')
+                       {
+                               // Chinese endpoint, e.g.: s3.cn-north-1.amazonaws.com.cn
+                               $regionalEndpoint = $regionalEndpoint . '.cn';
+                       }
+
+                       // If dual-stack URLs are enabled then prepend the endpoint
+                       if ($configuration->getDualstackUrl())
+                       {
+                               $endpoint = 's3.dualstack.' . $regionalEndpoint;
+                       }
+                       else
+                       {
+                               $endpoint = 's3.' . $regionalEndpoint;
+                       }
                }
 
                // Legacy path style access: return just the endpoint
index a8078b29389c3308acb99dbf6600c22a77027713..01810941f34b868571076173a5e7806b627818c7 100644 (file)
@@ -3,18 +3,18 @@
  * Akeeba Engine
  *
  * @package   akeebaengine
- * @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  * @license   GNU General Public License version 3, or later
  */
 
-namespace Akeeba\Engine\Postproc\Connector\S3v4;
+namespace Akeeba\S3;
 
-use Akeeba\Engine\Postproc\Connector\S3v4\Exception\PropertyNotFound;
-use Akeeba\Engine\Postproc\Connector\S3v4\Response\Error;
+use Akeeba\S3\Exception\PropertyNotFound;
+use Akeeba\S3\Response\Error;
 use SimpleXMLElement;
 
 // Protection against direct access
-defined('AKEEBAENGINE') or die();
+defined('AKEEBAENGINE') || die();
 
 /**
  * Amazon S3 API response object
@@ -124,7 +124,7 @@ class Response
         *
         * @param   string|SimpleXMLElement|null  $body
         */
-       public function setBody($body): void
+       public function setBody($body, bool $rawResponse = false): void
        {
                $this->body = null;
 
@@ -135,7 +135,7 @@ class Response
 
                $this->body = $body;
 
-               $this->finaliseBody();
+               $this->finaliseBody($rawResponse);
        }
 
        public function resetBody(): void
@@ -153,7 +153,7 @@ class Response
                $this->body .= $data;
        }
 
-       public function finaliseBody(): void
+       public function finaliseBody(bool $rawResponse = false): void
        {
                if (!$this->hasBody())
                {
@@ -165,8 +165,14 @@ class Response
                        $this->headers['type'] = 'text/plain';
                }
 
-               if (is_string($this->body) &&
-                       (($this->headers['type'] == 'application/xml') || (substr($this->body, 0, 5) == '<?xml'))
+               if (
+                       !$rawResponse
+                       && is_string($this->body)
+                       &&
+                       (
+                               ($this->headers['type'] == 'application/xml')
+                               || (substr($this->body, 0, 5) == '<?xml')
+                       )
                )
                {
                        $this->body = simplexml_load_string($this->body);
@@ -332,8 +338,8 @@ class Response
                )
                {
                        $this->error = new Error(
-                               $this->code,
-                               (string) $this->body->Message
+                               500,
+                               (string) $this->body->Code . ':' . (string) $this->body->Message
                        );
 
                        if (isset($this->body->Resource))
index ca40985d88b71114f1afcc706ea257ce4c8b9469..69710bdfbd26faa35adea46dd185ec656539cd74 100644 (file)
@@ -3,14 +3,14 @@
  * Akeeba Engine
  *
  * @package   akeebaengine
- * @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  * @license   GNU General Public License version 3, or later
  */
 
-namespace Akeeba\Engine\Postproc\Connector\S3v4\Response;
+namespace Akeeba\S3\Response;
 
 // Protection against direct access
-defined('AKEEBAENGINE') or die();
+defined('AKEEBAENGINE') || die();
 
 /**
  * S3 response error object
index c841ebe1fc55b377cf54605a5cccdd278551c559..93d8f0f6a0d5d69d1c51071caf5bd06f1b5510cd 100644 (file)
@@ -3,14 +3,14 @@
  * Akeeba Engine
  *
  * @package   akeebaengine
- * @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  * @license   GNU General Public License version 3, or later
  */
 
-namespace Akeeba\Engine\Postproc\Connector\S3v4;
+namespace Akeeba\S3;
 
 // Protection against direct access
-defined('AKEEBAENGINE') or die();
+defined('AKEEBAENGINE') || die();
 
 /**
  * Base class for request signing objects.
@@ -44,7 +44,7 @@ abstract class Signature
         */
        public static function getSignatureObject(Request $request, string $method = 'v2'): self
        {
-               $className = '\\Akeeba\\Engine\\Postproc\\Connector\\S3v4\\Signature\\' . ucfirst($method);
+               $className = __NAMESPACE__ . '\\Signature\\' . ucfirst($method);
 
                return new $className($request);
        }
index 86d9be7554181c4c1986566024586e247281d0f7..6864846fa88ce91e88d41c716141ea001e9ba9c5 100644 (file)
@@ -3,16 +3,16 @@
  * Akeeba Engine
  *
  * @package   akeebaengine
- * @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  * @license   GNU General Public License version 3, or later
  */
 
-namespace Akeeba\Engine\Postproc\Connector\S3v4\Signature;
+namespace Akeeba\S3\Signature;
 
 // Protection against direct access
-defined('AKEEBAENGINE') or die();
+defined('AKEEBAENGINE') || die();
 
-use Akeeba\Engine\Postproc\Connector\S3v4\Signature;
+use Akeeba\S3\Signature;
 
 /**
  * Implements the Amazon AWS v2 signatures
@@ -123,7 +123,7 @@ class V2 extends Signature
                }
 
                // AMZ headers must be sorted and sent as separate lines
-               if (sizeof($amz) > 0)
+               if (count($amz) > 0)
                {
                        sort($amz);
                        $amzString = "\n" . implode("\n", $amz);
@@ -150,8 +150,8 @@ class V2 extends Signature
                }
 
                $stringToSign = $verb . "\n" .
-                       (isset($headers['Content-MD5']) ? $headers['Content-MD5'] : '') . "\n" .
-                       (isset($headers['Content-Type']) ? $headers['Content-Type'] : '') . "\n" .
+                       ($headers['Content-MD5'] ?? '') . "\n" .
+                       ($headers['Content-Type'] ?? '') . "\n" .
                        $headers['Date'] .
                        $amzString . "\n" .
                        $resourcePath;
index e2bb908ee0865d09838ceed112f54204fc07d1e1..b39f81f4a2700384b06e918363b178bd0fdb3b38 100644 (file)
@@ -3,16 +3,16 @@
  * Akeeba Engine
  *
  * @package   akeebaengine
- * @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  * @license   GNU General Public License version 3, or later
  */
 
-namespace Akeeba\Engine\Postproc\Connector\S3v4\Signature;
+namespace Akeeba\S3\Signature;
 
 // Protection against direct access
-defined('AKEEBAENGINE') or die();
+defined('AKEEBAENGINE') || die();
 
-use Akeeba\Engine\Postproc\Connector\S3v4\Signature;
+use Akeeba\S3\Signature;
 use DateTime;
 
 /**
@@ -77,14 +77,20 @@ class V4 extends Signature
                 * http://s3-eu-west-1.amazonaws.com/example instead of http://example.amazonaws.com/ for all authenticated URLs
                 */
                $region   = $this->request->getConfiguration()->getRegion();
+               $bucket   = $this->request->getBucket();
                $hostname = $this->getPresignedHostnameForRegion($region);
+
+               if ($this->isValidBucketName($bucket))
+               {
+                       $hostname = $bucket . '.' . $hostname;
+               }
+
                $this->request->setHeader('Host', $hostname);
 
                // Set the expiration time in seconds
                $this->request->setHeader('Expires', (int) $lifetime);
 
                // Get the query parameters, including the calculated signature
-               $bucket           = $this->request->getBucket();
                $uri              = $this->request->getResource();
                $headers          = $this->request->getHeaders();
                $protocol         = $https ? 'https' : 'http';
@@ -93,6 +99,11 @@ class V4 extends Signature
                // The query parameters are returned serialized; unserialize them, then build and return the URL.
                $queryParameters = unserialize($serialisedParams);
 
+               if ($this->isValidBucketName($bucket) && strpos($uri, '/' . $bucket) === 0)
+               {
+                       $uri = substr($uri, strlen($bucket) + 1);
+               }
+
                $query = http_build_query($queryParameters);
 
                $url = $protocol . '://' . $headers['Host'] . $uri;
@@ -129,8 +140,8 @@ class V4 extends Signature
                $signatureDate = new DateTime($headers['Date']);
 
                $credentialScope = $signatureDate->format('Ymd') . '/' .
-                       $this->request->getConfiguration()->getRegion() . '/' .
-                       's3/aws4_request';
+                                  $this->request->getConfiguration()->getRegion() . '/' .
+                                  's3/aws4_request';
 
                /**
                 * If the Expires header is set up we're pre-signing a download URL. The string to sign is a bit
@@ -208,12 +219,14 @@ class V4 extends Signature
                // The canonical URI is the resource path
                $canonicalURI     = $resourcePath;
                $bucketResource   = '/' . $bucket;
-               $regionalHostname = ($headers['Host'] != 's3.amazonaws.com') && ($headers['Host'] != $bucket . '.s3.amazonaws.com');
+               $regionalHostname = ($headers['Host'] != 's3.amazonaws.com')
+                                   && ($headers['Host'] != $bucket . '.s3.amazonaws.com');
 
                // Special case: if the canonical URI ends in /?location the bucket name DOES count as part of the canonical URL
                // even though the Host is s3.amazonaws.com (in which case it normally shouldn't count). Yeah, I know, it makes
                // no sense!!!
-               if (!$regionalHostname && ($headers['Host'] == 's3.amazonaws.com') && (substr($canonicalURI, -10) == '/?location'))
+               if (!$regionalHostname && ($headers['Host'] == 's3.amazonaws.com')
+                   && (substr($canonicalURI, -10) == '/?location'))
                {
                        $regionalHostname = true;
                }
@@ -274,11 +287,11 @@ class V4 extends Signature
 
                // Calculate the canonical request
                $canonicalRequest = $verb . "\n" .
-                       $canonicalURI . "\n" .
-                       $canonicalQueryString . "\n" .
-                       $canonicalHeaders . "\n" .
-                       $signedHeaders . "\n" .
-                       $requestPayloadHash;
+                                   $canonicalURI . "\n" .
+                                   $canonicalQueryString . "\n" .
+                                   $canonicalHeaders . "\n" .
+                                   $signedHeaders . "\n" .
+                                   $requestPayloadHash;
 
                $hashedCanonicalRequest = hash('sha256', $canonicalRequest);
 
@@ -290,17 +303,40 @@ class V4 extends Signature
                        $headers['Date'] = '';
                }
 
+               /**
+                * The Date in the String-to-Sign is a messy situation.
+                *
+                * Amazon's documentation says it must be in ISO 8601 format: `Ymd\THis\Z`. Unfortunately, Amazon's
+                * documentation is actually wrong :troll_face: The actual Amazon S3 service expects the date to be formatted as
+                * per RFC1123.
+                *
+                * Most third party implementations have caught up to the fact that Amazon has documented the v4 signatures
+                * wrongly (naughty AWS!) and accept either format.
+                *
+                * Some other third party implementations, which never bothered to validate their implementations against Amazon
+                * S3 proper, only expect what Amazon has documented as "ISO 8601". Therefore, we detect third party services
+                * and switch to the as-documented date format.
+                *
+                * Some other third party services, like Wasabi, are broken in yet a different way. They will only use the date
+                * from the x-amz-date header, WITHOUT falling back to the Date header if the former is not present. This is
+                * the opposite of Amazon S3 proper which does expect the Date header. That's why the Request class sets both
+                * headers if the request is made to a service _other_ than Amazon S3 proper.
+                */
+               $dateToSignFor = strpos($headers['Host'], '.amazonaws.com') !== false
+                       ? $headers['Date']
+                       : $signatureDate->format('Ymd\THis\Z');
+
                $stringToSign = "AWS4-HMAC-SHA256\n" .
-                       $headers['Date'] . "\n" .
-                       $credentialScope . "\n" .
-                       $hashedCanonicalRequest;
+                               $dateToSignFor . "\n" .
+                               $credentialScope . "\n" .
+                               $hashedCanonicalRequest;
 
                if ($isPresignedURL)
                {
                        $stringToSign = "AWS4-HMAC-SHA256\n" .
-                               $parameters['X-Amz-Date'] . "\n" .
-                               $credentialScope . "\n" .
-                               $hashedCanonicalRequest;
+                                       $parameters['X-Amz-Date'] . "\n" .
+                                       $credentialScope . "\n" .
+                                       $hashedCanonicalRequest;
                }
 
                // ========== Step 3: Calculate the signature ==========
@@ -313,9 +349,9 @@ class V4 extends Signature
                // See http://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html
 
                $authorization = 'AWS4-HMAC-SHA256 Credential=' .
-                       $this->request->getConfiguration()->getAccess() . '/' . $credentialScope . ', ' .
-                       'SignedHeaders=' . $signedHeaders . ', ' .
-                       'Signature=' . $signature;
+                                $this->request->getConfiguration()->getAccess() . '/' . $credentialScope . ', ' .
+                                'SignedHeaders=' . $signedHeaders . ', ' .
+                                'Signature=' . $signature;
 
                // For presigned URLs we only return the Base64-encoded signature without the AWS format specifier and the
                // public access key.
@@ -366,7 +402,14 @@ class V4 extends Signature
         */
        private function getPresignedHostnameForRegion(string $region): string
        {
-               $endpoint         = 's3.' . $region . '.amazonaws.com';
+               $config   = $this->request->getConfiguration();
+               $endpoint = $config->getEndpoint();
+
+               if (empty($endpoint))
+               {
+                       $endpoint = 's3.' . $region . '.amazonaws.com';
+               }
+
                $dualstackEnabled = $this->request->getConfiguration()->getDualstackUrl();
 
                // If dual-stack URLs are enabled then prepend the endpoint
@@ -382,4 +425,83 @@ class V4 extends Signature
 
                return $endpoint;
        }
+
+       /**
+        * Is this a valid bucket name?
+        *
+        * @param   string  $bucketName   The bucket name to check
+        * @param   bool    $asSubdomain  Should I put additional restrictions for use as a subdomain?
+        *
+        * @return  bool
+        * @since   2.3.1
+        *
+        * @see     https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html
+        */
+       private function isValidBucketName(string $bucketName, bool $asSubdomain = true): bool
+       {
+               /**
+                * If there are dots in the bucket name I can't use it as a subdomain.
+                *
+                * "If you include dots in a bucket's name, you can't use virtual-host-style addressing over HTTPS, unless you
+                * perform your own certificate validation. This is because the security certificates used for virtual hosting
+                * of buckets don't work for buckets with dots in their names."
+                */
+               if ($asSubdomain && strpos($bucketName, '.') !== false)
+               {
+                       return false;
+               }
+
+               /**
+                * - Bucket names must be between 3 (min) and 63 (max) characters long.
+                * - Bucket names can consist only of lowercase letters, numbers, dots (.), and hyphens (-).
+                */
+               if (!preg_match('/^[a-z0-9\-.]{3,63}$/', $bucketName))
+               {
+                       return false;
+               }
+
+               // Bucket names must begin and end with a letter or number.
+               if (!preg_match('/^[a-z0-9].*[a-z0-9]$/', $bucketName))
+               {
+                       return false;
+               }
+
+               // Bucket names must not contain two adjacent periods.
+               if (preg_match('/\.\./', $bucketName))
+               {
+                       return false;
+               }
+
+               // Bucket names must not be formatted as an IP address (for example, 192.168.5.4).
+               if (preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/', $bucketName))
+               {
+                       return false;
+               }
+
+               // Bucket names must not start with the prefix xn--.
+               if (strpos($bucketName, 'xn--') === 0)
+               {
+                       return false;
+               }
+
+               // Bucket names must not start with the prefix sthree- and the prefix sthree-configurator.
+               if (strpos($bucketName, 'sthree-') === 0)
+               {
+                       return false;
+               }
+
+               // Bucket names must not end with the suffix -s3alias.
+               if (substr($bucketName, -8) === '-s3alias')
+               {
+                       return false;
+               }
+
+               // Bucket names must not end with the suffix --ol-s3.
+               if (substr($bucketName, -7) === '--ol-s3')
+               {
+                       return false;
+               }
+
+               return true;
+       }
 }
index 53ea310f50b773400c694889020f3036d9198f78..0c213af2445fcce7b9b792f21550f877dcbebc17 100644 (file)
@@ -3,11 +3,11 @@
  * Akeeba Engine
  *
  * @package   akeebaengine
- * @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  * @license   GNU General Public License version 3, or later
  */
 
-namespace Akeeba\Engine\Postproc\Connector\S3v4;
+namespace Akeeba\S3;
 
 /**
  * Amazon S3 Storage Classes
@@ -28,39 +28,39 @@ class StorageClass
        /**
         * Amazon S3 Standard (S3 Standard)
         */
-       const STANDARD = 'STANDARD';
+       public const STANDARD = 'STANDARD';
 
        /**
         * Reduced redundancy storage
         *
         * Not recommended anymore. Use INTELLIGENT_TIERING instead.
         */
-       const REDUCED_REDUNDANCY = 'REDUCED_REDUNDANCY';
+       public const REDUCED_REDUNDANCY = 'REDUCED_REDUNDANCY';
 
        /**
         * Amazon S3 Intelligent-Tiering (S3 Intelligent-Tiering)
         */
-       const INTELLIGENT_TIERING = 'INTELLIGENT_TIERING';
+       public const INTELLIGENT_TIERING = 'INTELLIGENT_TIERING';
 
        /**
         * Amazon S3 Standard-Infrequent Access (S3 Standard-IA)
         */
-       const STANDARD_IA = 'STANDARD_IA';
+       public const STANDARD_IA = 'STANDARD_IA';
 
        /**
         * Amazon S3 One Zone-Infrequent Access (S3 One Zone-IA)
         */
-       const ONEZONE_IA = 'ONEZONE_IA';
+       public const ONEZONE_IA = 'ONEZONE_IA';
 
        /**
         * Amazon S3 Glacier (S3 Glacier)
         */
-       const GLACIER = 'GLACIER';
+       public const GLACIER = 'GLACIER';
 
        /**
         * Amazon S3 Glacier Deep Archive (S3 Glacier Deep Archive)
         */
-       const DEEP_ARCHIVE = 'DEEP_ARCHIVE';
+       public const DEEP_ARCHIVE = 'DEEP_ARCHIVE';
 
        /**
         * Manipulate the $headers array, setting the X-Amz-Storage-Class header for the requested storage class.
diff --git a/s3_storage/vendor/akeeba/s3/src/aliasing.php b/s3_storage/vendor/akeeba/s3/src/aliasing.php
new file mode 100644 (file)
index 0000000..4b408ac
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+/**
+ * Akeeba Engine
+ *
+ * @package   akeebaengine
+ * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @license   GNU General Public License version 3, or later
+ */
+
+/**
+ * Automatic aliasing of the old namespace to the new, as you use each old class.
+ */
+spl_autoload_register(
+       static function (string $className)
+       {
+               $oldNS = 'Akeeba\Engine\Postproc\Connector\S3v4';
+               $newNS = 'Akeeba\S3';
+
+               $className = trim($className, '\\');
+
+               if (strpos($className, $oldNS) !== 0)
+               {
+                       return false;
+               }
+
+               $newClassName = $newNS . '\\' . trim(substr($className, strlen($oldNS)), '\\');
+
+               if (class_exists($newClassName, true))
+               {
+                       class_alias($newClassName, $className, false);
+               }
+
+               return true;
+       }
+);
index bc699a1811a82c5ef18288aabfefeb4b961162ad..8da34f5d5383167f51f18c97fe8479f4fcdbc31d 100644 (file)
@@ -6,31 +6,31 @@ $vendorDir = dirname(dirname(__FILE__));
 $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',
 );
diff --git a/s3_storage/vendor/composer/autoload_files.php b/s3_storage/vendor/composer/autoload_files.php
new file mode 100644 (file)
index 0000000..cc01550
--- /dev/null
@@ -0,0 +1,10 @@
+<?php
+
+// autoload_files.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+    '714ccd4b330431237faf946f71c4c9a4' => $vendorDir . '/akeeba/s3/src/aliasing.php',
+);
index b1215e7f59c48df3f32d3f7d25d3e2708ded056f..400320bf047386a5c300a536c3b6885e37ad04fa 100644 (file)
@@ -6,5 +6,5 @@ $vendorDir = dirname(dirname(__FILE__));
 $baseDir = dirname($vendorDir);
 
 return array(
-    'Akeeba\\Engine\\Postproc\\Connector\\S3v4\\' => array($vendorDir . '/akeeba/s3/src'),
+    'Akeeba\\S3\\' => array($vendorDir . '/akeeba/s3/src'),
 );
index a5d4ee9a74155fb7116d226c4264887763dbac17..9df2057bb968e4bc8fa3936a85e3b13328bf9902 100644 (file)
@@ -50,6 +50,24 @@ class ComposerAutoloaderInitS3StorageAddon
 
         $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;
+    }
+}
index 09179f94f27fb3729a0118d8480c2209494452dc..929f8194b7d03f50fbe2a7df28bc7616f7b6ccde 100644 (file)
@@ -6,48 +6,52 @@ namespace Composer\Autoload;
 
 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)
index f122e1ba016a3de8b1d6852c654589ea889bf433..70c17e11cb04a4da37c3ab340b9ca0be13ba414a 100644 (file)
@@ -1,35 +1,38 @@
 [
     {
         "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": [
             {