$rest = new UpdraftPlus_S3Request('PUT', $bucket, '', $this->endpoint, $this->use_dns_bucket_name, $this);
$rest->setParameter('logging', null);
$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::setBucketLogging({$bucket}, {$targetBucket}): [%s] %s",
$rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
* Get logging status for a bucket
* This will return false if logging is not enabled.
* Note: To enable logging, you also need to grant write access to the log group
* @param string $bucket Bucket name
public function getBucketLogging($bucket) {
$rest = new UpdraftPlus_S3Request('GET', $bucket, '', $this->endpoint, $this->use_dns_bucket_name, $this);
$rest->setParameter('logging', null);
$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::getBucketLogging({$bucket}): [%s] %s",
$rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
if (!isset($rest->body->LoggingEnabled)) return false; // No logging
'targetBucket' => (string)$rest->body->LoggingEnabled->TargetBucket,
'targetPrefix' => (string)$rest->body->LoggingEnabled->TargetPrefix,
* @param string $bucket Bucket name
public function disableBucketLogging($bucket) {
return $this->setBucketLogging($bucket, null);
* Get a bucket's location
* @param string $bucket Bucket name
public function getBucketLocation($bucket) {
$rest = new UpdraftPlus_S3Request('GET', $bucket, '', $this->endpoint, $this->use_dns_bucket_name, $this);
$rest->setParameter('location', null);
$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::getBucketLocation({$bucket}): [%s] %s",
$rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
return (isset($rest->body[0]) && (string)$rest->body[0] !== '') ? (string)$rest->body[0] : 'US';
* Set object or bucket Access Control Policy
* @param string $bucket Bucket name
* @param string $uri Object URI
* @param array $acp Access Control Policy Data (same as the data returned from getAccessControlPolicy)
public function setAccessControlPolicy($bucket, $uri = '', $acp = array()) {
$dom->formatOutput = true;
$accessControlPolicy = $dom->createElement('AccessControlPolicy');
$accessControlList = $dom->createElement('AccessControlList');
// It seems the owner has to be passed along too
$owner = $dom->createElement('Owner');
$owner->appendChild($dom->createElement('ID', $acp['owner']['id']));
$owner->appendChild($dom->createElement('DisplayName', $acp['owner']['name']));
$accessControlPolicy->appendChild($owner);
foreach ($acp['acl'] as $g) {
$grant = $dom->createElement('Grant');
$grantee = $dom->createElement('Grantee');
$grantee->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
// CanonicalUser (DisplayName is omitted)
$grantee->setAttribute('xsi:type', 'CanonicalUser');
$grantee->appendChild($dom->createElement('ID', $g['id']));
} elseif (isset($g['email'])) {
$grantee->setAttribute('xsi:type', 'AmazonCustomerByEmail');
$grantee->appendChild($dom->createElement('EmailAddress', $g['email']));
} elseif ('Group' == $g['type']) {
$grantee->setAttribute('xsi:type', 'Group');
$grantee->appendChild($dom->createElement('URI', $g['uri']));
$grant->appendChild($grantee);
$grant->appendChild($dom->createElement('Permission', $g['permission']));
$accessControlList->appendChild($grant);
$accessControlPolicy->appendChild($accessControlList);
$dom->appendChild($accessControlPolicy);
$rest = new UpdraftPlus_S3Request('PUT', $bucket, $uri, $this->endpoint, $this->use_dns_bucket_name, $this);
$rest->setParameter('acl', null);
$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::setAccessControlPolicy({$bucket}, {$uri}): [%s] %s",
$rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
* Get object or bucket Access Control Policy
* @param string $bucket Bucket name
* @param string $uri Object URI
public function getAccessControlPolicy($bucket, $uri = '') {
$rest = new UpdraftPlus_S3Request('GET', $bucket, $uri, $this->endpoint, $this->use_dns_bucket_name, $this);
$rest->setParameter('acl', null);
$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::getAccessControlPolicy({$bucket}, {$uri}): [%s] %s",
$rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
if (isset($rest->body->Owner, $rest->body->Owner->ID, $rest->body->Owner->DisplayName))
'id' => (string)$rest->body->Owner->ID, 'name' => (string)$rest->body->Owner->DisplayName
if (isset($rest->body->AccessControlList)) {
foreach ($rest->body->AccessControlList->Grant as $grant) {
foreach ($grant->Grantee as $grantee) {
if (isset($grantee->ID, $grantee->DisplayName)) // CanonicalUser
'type' => 'CanonicalUser',
'id' => (string)$grantee->ID,
'name' => (string)$grantee->DisplayName,
'permission' => (string)$grant->Permission
elseif (isset($grantee->EmailAddress)) // AmazonCustomerByEmail
'type' => 'AmazonCustomerByEmail',
'email' => (string)$grantee->EmailAddress,
'permission' => (string)$grant->Permission
elseif (isset($grantee->URI)) // Group
'uri' => (string)$grantee->URI,
'permission' => (string)$grant->Permission
* @param string $bucket Bucket name
* @param string $uri Object URI
public function deleteObject($bucket, $uri) {
$rest = new UpdraftPlus_S3Request('DELETE', $bucket, $uri, $this->endpoint, $this->use_dns_bucket_name, $this);
$rest = $rest->getResponse();
if (false === $rest->error && 204 !== $rest->code) {
$rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
if (false !== $rest->error) {
$this->__triggerError(sprintf("UpdraftPlus_S3::deleteObject(): [%s] %s",
$rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
* Get a query string authenticated URL
* @param string $bucket Bucket name
* @param string $uri Object URI
* @param integer $lifetime Lifetime in seconds
* @param boolean $hostBucket Use the bucket name as the hostname
* @param boolean $https Use HTTPS ($hostBucket should be false for SSL verification)
public function getAuthenticatedURL($bucket, $uri, $lifetime, $hostBucket = false, $https = false) {
$expires = time() + $lifetime;
$uri = str_replace(array('%2F', '%2B'), array('/', '+'), rawurlencode($uri));
return sprintf(($https ? 'https' : 'http').'://%s/%s?AWSAccessKeyId=%s&Expires=%u&Signature=%s',
// $hostBucket ? $bucket : $bucket.'.s3.amazonaws.com', $uri, $this->__accessKey, $expires,
$hostBucket ? $bucket : 's3.amazonaws.com/'.$bucket, $uri, $this->__accessKey, $expires,
urlencode($this->__getHash("GET\n\n\n{$expires}\n/{$bucket}/{$uri}")));
* Get upload POST parameters for form uploads
* @param string $bucket Bucket name
* @param string $uriPrefix Object URI prefix
* @param string $acl ACL constant
* @param integer $lifetime Lifetime in seconds
* @param integer $maxFileSize Maximum file size in bytes (default 5MB)
* @param string $successRedirect Redirect URL or 200 / 201 status code
* @param array $amzHeaders Array of x-amz-meta-* headers
* @param array $headers Array of request headers or content type as a string
* @param boolean $flashVars Includes additional "Filename" variable posted by Flash
public function getHttpUploadPostParams($bucket, $uriPrefix = '', $acl = self::ACL_PRIVATE, $lifetime = 3600,
$maxFileSize = 5242880, $successRedirect = "201", $amzHeaders = array(), $headers = array(), $flashVars = false) {
$policy->expiration = gmdate('Y-m-d\TH:i:s\Z', (time() + $lifetime));
$policy->conditions = array();
$obj = new stdClass; $obj->bucket = $bucket; array_push($policy->conditions, $obj);
$obj = new stdClass; $obj->acl = $acl; array_push($policy->conditions, $obj);
$obj = new stdClass; // 200 for non-redirect uploads
if (is_numeric($successRedirect) && in_array((int)$successRedirect, array(200, 201)))
$obj->success_action_status = (string)$successRedirect;
$obj->success_action_redirect = $successRedirect;
array_push($policy->conditions, $obj);
if (self::ACL_PUBLIC_READ !== $acl)
array_push($policy->conditions, array('eq', '$acl', $acl));
array_push($policy->conditions, array('starts-with', '$key', $uriPrefix));
if ($flashVars) array_push($policy->conditions, array('starts-with', '$Filename', ''));
foreach (array_keys($headers) as $headerKey)
array_push($policy->conditions, array('starts-with', '$'.$headerKey, ''));
foreach ($amzHeaders as $headerKey => $headerVal) {
$obj->{$headerKey} = (string)$headerVal;
array_push($policy->conditions, $obj);
array_push($policy->conditions, array('content-length-range', 0, $maxFileSize));
$policy = base64_encode(str_replace('\/', '/', json_encode($policy)));
$params->AWSAccessKeyId = $this->__accessKey;
$params->key = $uriPrefix.'${filename}';
$params->policy = $policy; unset($policy);
$params->signature = $this->__getHash($params->policy);
if (is_numeric($successRedirect) && in_array((int)$successRedirect, array(200, 201)))
$params->success_action_status = (string)$successRedirect;
$params->success_action_redirect = $successRedirect;
foreach ($headers as $headerKey => $headerVal) $params->{$headerKey} = (string)$headerVal;
foreach ($amzHeaders as $headerKey => $headerVal) $params->{$headerKey} = (string)$headerVal;
* @internal Used to get mime types
* @param string &$file File path
public function __getMimeType(&$file) {// 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.
// Fileinfo documentation says fileinfo_open() will use the
// MAGIC env var for the magic file
if (extension_loaded('fileinfo') && isset($_ENV['MAGIC']) &&
false !== ($finfo = finfo_open(FILEINFO_MIME, $_ENV['MAGIC']))) {// phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.finfo_openFound -- The function finfo_open() is not present in PHP version 5.2 or earlier
if (false !== ($type = finfo_file($finfo, $file))) {// phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.finfo_fileFound -- The function finfo_file() is not present in PHP version 5.2 or earlier
// Remove the charset and grab the last content-type
$type = explode(' ', str_replace('; charset=', ';charset=', $type));
$type = array_pop($type);
$type = explode(';', $type);
$type = trim(array_shift($type));
finfo_close($finfo);// phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.finfo_closeFound -- The function finfo_close() is not present in PHP version 5.2 or earlier
// If anyone is still using mime_content_type()
} elseif (function_exists('mime_content_type')) {
$type = trim(mime_content_type($file));
if (false !== $type && strlen($type) > 0) return $type;
// Otherwise do it the old fashioned way
'jpg' => 'image/jpeg', 'gif' => 'image/gif', 'png' => 'image/png',
'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'ico' => 'image/x-icon',
'swf' => 'application/x-shockwave-flash', 'pdf' => 'application/pdf',
'zip' => 'application/zip', 'gz' => 'application/x-gzip',
'tar' => 'application/x-tar', 'bz' => 'application/x-bzip',
'bz2' => 'application/x-bzip2', 'txt' => 'text/plain',
'asc' => 'text/plain', 'htm' => 'text/html', 'html' => 'text/html',
'css' => 'text/css', 'js' => 'text/javascript',
'xml' => 'text/xml', 'xsl' => 'application/xsl+xml',
'ogg' => 'application/ogg', 'mp3' => 'audio/mpeg', 'wav' => 'audio/x-wav',
'avi' => 'video/x-msvideo', 'mpg' => 'video/mpeg', 'mpeg' => 'video/mpeg',
'mov' => 'video/quicktime', 'flv' => 'video/x-flv', 'php' => 'text/x-php'
$ext = strtolower(pathInfo($file, PATHINFO_EXTENSION));
return isset($exts[$ext]) ? $exts[$ext] : 'application/octet-stream';
* Generate the auth string: "AWS AccessKey:Signature"
* @internal Used by UpdraftPlus_S3Request::getResponse()
* @param string $string String to sign
public function __getSignature($string) {// 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.
return 'AWS '.$this->__accessKey.':'.$this->__getHash($string);
* Creates a HMAC-SHA1 hash
* This uses the hash extension if loaded
* @internal Used by __getSignature()
* @param string $string String to sign
private function __getHash($string) {// 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.
return base64_encode(extension_loaded('hash') ?
hash_hmac('sha1', $string, $this->__secretKey, true) : pack('H*', sha1(
(str_pad($this->__secretKey, 64, chr(0x00)) ^ (str_repeat(chr(0x5c), 64))) .
pack('H*', sha1((str_pad($this->__secretKey, 64, chr(0x00)) ^
(str_repeat(chr(0x36), 64))) . $string)))));
* Generate the headers for AWS Signature V4
* @internal Used by UpdraftPlus_S3Request::getResponse()
* @param array $aHeaders amzHeaders
public function __getSignatureV4($aHeaders, $headers, $method = 'GET', $uri = '', $data = '') {// 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.
$region = $this->getRegion();
$algorithm = 'AWS4-HMAC-SHA256';
$amzDate = gmdate('Ymd\THis\Z');
$amzDateStamp = gmdate('Ymd');
// amz-date ISO8601 format? for aws request
$amzHeaders['x-amz-date'] = $amzDate;
foreach ($headers as $k => $v) {
$amzHeaders[strtolower($k)] = trim($v);
foreach ($aHeaders as $k => $v) {
$amzHeaders[strtolower($k)] = trim($v);
uksort($amzHeaders, 'strcmp');
$payloadHash = isset($amzHeaders['x-amz-content-sha256']) ? $amzHeaders['x-amz-content-sha256'] : hash('sha256', $data);
list($uri, $query_str) = @explode('?', $uri);
parse_str($query_str, $parameters);
$amzRequests[] = $method;
$uriQmPos = strpos($uri, '?');
$amzRequests[] = (false === $uriQmPos ? $uri : substr($uri, 0, $uriQmPos));
$amzRequests[] = http_build_query($parameters);
// add headers as string to requests
foreach ($amzHeaders as $k => $v) {
$amzRequests[] = $k . ':' . $v;
// add a blank entry so we end up with an extra line break
$amzRequests[] = implode(';', array_keys($amzHeaders));
$amzRequests[] = $payloadHash;
$amzRequestStr = implode("\n", $amzRequests);
$credentialScope = array();
$credentialScope[] = $amzDateStamp;
$credentialScope[] = $region;
$credentialScope[] = $service;
$credentialScope[] = 'aws4_request';
$stringToSign[] = $algorithm;
$stringToSign[] = $amzDate;
$stringToSign[] = implode('/', $credentialScope);
$stringToSign[] = hash('sha256', $amzRequestStr);
$stringToSignStr = implode("\n", $stringToSign);
$kSecret = 'AWS4' . $this->__secretKey;
$kDate = hash_hmac('sha256', $amzDateStamp, $kSecret, true);
$kRegion = hash_hmac('sha256', $region, $kDate, true);
$kService = hash_hmac('sha256', $service, $kRegion, true);
$kSigning = hash_hmac('sha256', 'aws4_request', $kService, true);
$signature = hash_hmac('sha256', $stringToSignStr, $kSigning);
'Credential=' . $this->__accessKey . '/' . implode('/', $credentialScope),
'SignedHeaders=' . implode(';', array_keys($amzHeaders)),
'Signature=' . $signature,
$authorizationStr = $algorithm . ' ' . implode(',', $authorization);
'X-AMZ-DATE' => $amzDate,
'Authorization' => $authorizationStr