* Copyright (c) 2011, Donovan Schönknecht. All rights reserved.
* Portions copyright (c) 2012-2021, David Anderson (https://david.dw-perspective.org.uk). All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* Amazon S3 is a trademark of Amazon.com, Inc. or its affiliates.
* Forked originally from:
* @link http://undesigned.org.za/2007/10/22/amazon-s3-php-class
const ACL_PRIVATE = 'private';
const ACL_PUBLIC_READ = 'public-read';
const ACL_PUBLIC_READ_WRITE = 'public-read-write';
const ACL_AUTHENTICATED_READ = 'authenticated-read';
const STORAGE_CLASS_STANDARD = 'STANDARD';
private $__accessKey = null; // AWS Access key
private $__secretKey = null; // AWS Secret key
private $__sslKey = null;
private $__session_token = null; //For Vault temporary users
private $_serverSideEncryption = false;
public $endpoint = 's3.amazonaws.com';
public $region = 'us-east-1';
// Added to cope with a particular situation where the user had no permission to check the bucket location, which necessitated using DNS-based endpoints.
public $use_dns_bucket_name = false;
public $useSSLValidation = true;
public $useExceptions = false;
// Added at request of a user using a non-default port.
// SSL CURL SSL options - only needed if you are experiencing problems with your OpenSSL configuration
public $sslCACert = null;
private $__signingKeyPairId = null; // AWS Key Pair ID
private $__signingKeyResource = false; // Key resource, freeSigningKey() must be called to clear it from memory
* Constructor - if you're not using the class statically
* @param string $accessKey Access key
* @param string $secretKey Secret key
* @param boolean $useSSL Enable SSL
* @param boolean $sslCACert SSL Certificate
* @param string|null $endpoint Endpoint
* @param string $session_token The session token returned by AWS for temporary credentials access
* @param string $region Region
* @throws Exception If cURL extension is not present
public function __construct($accessKey = null, $secretKey = null, $useSSL = true, $sslCACert = true, $endpoint = null, $session_token = null, $region = 'us-east-1') {
if (null !== $accessKey && null !== $secretKey) {
$this->setAuth($accessKey, $secretKey, $session_token);
$this->setSSL($useSSL, !empty($sslCACert));
$this->sslCACert = $sslCACert;
$this->endpoint = $endpoint;
if (!function_exists('curl_init')) {
$updraftplus->log('The PHP cURL extension must be installed and enabled to use this remote storage method');
throw new Exception('The PHP cURL extension must be installed and enabled to use this remote storage method');
* Set the service endpoint
* @param string $host Hostname
public function setEndpoint($host) {
* Set Server Side Encryption
* Example value: 'AES256'. See: https://docs.aws.amazon.com/AmazonS3/latest/dev/SSEUsingPHPSDK.html
* @param string|boolean $sse Server side encryption standard; or false for none
public function setServerSideEncryption($value) {
$this->_serverSideEncryption = $value;
* @param string $region Region
public function setRegion($region) {
* Note: Region calculation will be done in methods/s3.php file
public function getRegion() {
* @param Integer $port Port number
public function setPort($port) {
* Set AWS access key and secret key
* @param string $accessKey Access key
* @param string $secretKey Secret key
public function setAuth($accessKey, $secretKey, $session_token = null) {
$this->__accessKey = $accessKey;
$this->__secretKey = $secretKey;
$this->__session_token = $session_token;
* Check if AWS keys have been set
public function hasAuth() {
return (null !== $this->__accessKey && null !== $this->__secretKey);
* @param boolean $enabled SSL enabled
* @param boolean $validate SSL certificate validation
public function setSSL($enabled, $validate = true) {
$this->useSSL = $enabled;
$this->useSSLValidation = $validate;
* Get SSL value. Determines whether use it or not.
public function getuseSSL() {
* Set SSL client certificates (experimental)
* @param string $sslCert SSL client certificate
* @param string $sslKey SSL client key
* @param string $sslCACert SSL CA cert (only required if you are having problems with your system CA cert)
public function setSSLAuth($sslCert = null, $sslKey = null, $sslCACert = null) {
$this->sslCert = $sslCert;
$this->sslCACert = $sslCACert;
* @param string $host Proxy hostname and port (localhost:1234)
* @param string $user Proxy username
* @param string $pass Proxy password
* @param integer $type CURL proxy type
public function setProxy($host, $user = null, $pass = null, $type = CURLPROXY_SOCKS5, $port = null) {
$this->proxy = array('host' => $host, 'type' => $type, 'user' => $user, 'pass' => $pass, 'port' => $port);
* Set the error mode to exceptions
* @param boolean $enabled Enable exceptions
public function setExceptions($enabled = true) {
$this->useExceptions = $enabled;
* @param string $keyPairId AWS Key Pair ID
* @param string $signingKey Private Key
* @param boolean $isFile Load private key from file, set to false to load string
public function setSigningKey($keyPairId, $signingKey, $isFile = true) {
$this->__signingKeyPairId = $keyPairId;
if (($this->__signingKeyResource = openssl_pkey_get_private($isFile ?
file_get_contents($signingKey) : $signingKey)) !== false) return true;
$this->__triggerError('UpdraftPlus_S3::setSigningKey(): Unable to open load private key: '.$signingKey, __FILE__, __LINE__);
* Free signing key from memory, MUST be called if you are using setSigningKey()
public function freeSigningKey() {
if (false !== $this->__signingKeyResource) {
openssl_free_key($this->__signingKeyResource);
public function setSignatureVersion($version = 'v2') {
$this->signVer = $version;
* @param string $message Error message
* @param string $file Filename
* @param integer $line Line number
* @param integer $code Error code
* @internal Internal error handler
* @throws UpdraftPlus_S3Exception
private function __triggerError($message, $file, $line, $code = 0) {// phpcs:ignore PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.MethodDoubleUnderscore -- Method name "UpdraftPlus_S3Request::__responseHeaderCallback" is discouraged; PHP has reserved all method names with a double underscore prefix for future use.
if ($this->useExceptions) {
throw new UpdraftPlus_S3Exception($message, $file, $line, $code);
trigger_error($message, E_USER_WARNING);
* @param boolean $detailed Returns detailed bucket list when true
public function listBuckets($detailed = false) {
$rest = new UpdraftPlus_S3Request('GET', '', '', $this->endpoint, $this->use_dns_bucket_name, $this);
$rest = $rest->getResponse();
if (false === $rest->error && 200 !== $rest->code) {
$rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
if (false !== $rest->error) {
$this->__triggerError(sprintf("UpdraftPlus_S3::listBuckets(): [%s] %s", $rest->error['code'],
$rest->error['message']), __FILE__, __LINE__);
if (!isset($rest->body->Buckets)) return $results;
if (isset($rest->body->Owner, $rest->body->Owner->ID, $rest->body->Owner->DisplayName))
$results['owner'] = array(
'id' => (string)$rest->body->Owner->ID, 'name' => (string)$rest->body->Owner->ID
$results['buckets'] = array();
foreach ($rest->body->Buckets->Bucket as $b) {
$results['buckets'][] = array(
'name' => (string)$b->Name, 'time' => strtotime((string)$b->CreationDate)
foreach ($rest->body->Buckets->Bucket as $b) $results[] = (string)$b->Name;
public function useDNSBucketName($use = true, $bucket = '') {
$this->use_dns_bucket_name = $use;
* Get contents for a bucket
* If maxKeys is null this method will loop through truncated result sets
* @param string $bucket Bucket name
* @param string $prefix Prefix
* @param string $marker Marker (last file listed)
* @param string $maxKeys Max keys (maximum number of keys to return)
* @param string $delimiter Delimiter
* @param boolean $returnCommonPrefixes Set to true to return CommonPrefixes
public function getBucket($bucket, $prefix = null, $marker = null, $maxKeys = null, $delimiter = null, $returnCommonPrefixes = false) {
$rest = new UpdraftPlus_S3Request('GET', $bucket, '', $this->endpoint, $this->use_dns_bucket_name, $this);
if (0 == $maxKeys) $maxKeys = null;
if (!empty($prefix)) $rest->setParameter('prefix', $prefix);
if (!empty($marker)) $rest->setParameter('marker', $marker);
if (!empty($maxKeys)) $rest->setParameter('max-keys', $maxKeys);
if (!empty($delimiter)) $rest->setParameter('delimiter', $delimiter);
$response = $rest->getResponse();
if (false === $response->error && 200 !== $response->code) {
$response->error = array('code' => $response->code, 'message' => 'Unexpected HTTP status');
if (false !== $response->error) {
$this->__triggerError(sprintf("UpdraftPlus_S3::getBucket(): [%s] %s",
$response->error['code'], $response->error['message']), __FILE__, __LINE__);
if (isset($response->body, $response->body->Contents))
foreach ($response->body->Contents as $c) {
$results[(string)$c->Key] = array(
'name' => (string)$c->Key,
'time' => strtotime((string)$c->LastModified),
'hash' => substr((string)$c->ETag, 1, -1)
$nextMarker = (string)$c->Key;
if ($returnCommonPrefixes && isset($response->body, $response->body->CommonPrefixes))
foreach ($response->body->CommonPrefixes as $c)
$results[(string)$c->Prefix] = array('prefix' => (string)$c->Prefix);
if (isset($response->body, $response->body->IsTruncated) &&
(string)$response->body->IsTruncated == 'false') return $results;
if (isset($response->body, $response->body->NextMarker))
$nextMarker = (string)$response->body->NextMarker;
// Loop through truncated results if maxKeys isn't specified
if (null == $maxKeys && null !== $nextMarker && 'true' == (string)$response->body->IsTruncated)
$rest = new UpdraftPlus_S3Request('GET', $bucket, '', $this->endpoint, $this->use_dns_bucket_name, $this);
if (!empty($prefix)) $rest->setParameter('prefix', $prefix);
$rest->setParameter('marker', $nextMarker);
if (!empty($delimiter)) $rest->setParameter('delimiter', $delimiter);
if (false == ($response = $rest->getResponse()) || 200 !== $response->code) break;
if (isset($response->body, $response->body->Contents))
foreach ($response->body->Contents as $c)
$results[(string)$c->Key] = array(
'name' => (string)$c->Key,
'time' => strtotime((string)$c->LastModified),
'hash' => substr((string)$c->ETag, 1, -1)
$nextMarker = (string)$c->Key;
if ($returnCommonPrefixes && isset($response->body, $response->body->CommonPrefixes))
foreach ($response->body->CommonPrefixes as $c)
$results[(string)$c->Prefix] = array('prefix' => (string)$c->Prefix);
if (isset($response->body, $response->body->NextMarker))
$nextMarker = (string)$response->body->NextMarker;
} while (false !== $response && 'true' == (string)$response->body->IsTruncated);
* @param string $bucket Bucket name
* @param string ACL_PRIVATE ACL flag
* @param mixed $location Set as "EU" to create buckets hosted in Europe
public function putBucket($bucket, $acl = self::ACL_PRIVATE, $location = false) {
$rest = new UpdraftPlus_S3Request('PUT', $bucket, '', $this->endpoint, $this->use_dns_bucket_name, $this);
$rest->setAmzHeader('x-amz-acl', $acl);
if (false === $location) $location = $this->getRegion();
if (false !== $location && 'us-east-1' !== $location) {
$createBucketConfiguration = $dom->createElement('CreateBucketConfiguration');
$locationConstraint = $dom->createElement('LocationConstraint', $location);
$createBucketConfiguration->appendChild($locationConstraint);
$dom->appendChild($createBucketConfiguration);
$rest->data = $dom->saveXML();
$rest->size = strlen($rest->data);
$rest->setHeader('Content-Type', 'application/xml');
$rest = $rest->getResponse();
if (false === $rest->error && 200 !== $rest->code) {
$rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
if (false !== $rest->error) {
$this->__triggerError(sprintf("UpdraftPlus_S3::putBucket({$bucket}, {$acl}, {$location}): [%s] %s",
$rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
* @param string $bucket Bucket name
public function deleteBucket($bucket) {
$rest = new UpdraftPlus_S3Request('DELETE', $bucket, '', $this->endpoint, $this->use_dns_bucket_name, $this);
$rest = $rest->getResponse();