PluginProbe ʕ •ᴥ•ʔ
UpdraftPlus: WP Backup & Migration Plugin / 1.9.50
UpdraftPlus: WP Backup & Migration Plugin v1.9.50
1.26.4 1.26.3 1.9.19 1.9.25 1.9.26 1.9.30 1.9.31 1.9.32 1.9.4 1.9.40 1.9.41 1.9.42 1.9.43 1.9.44 1.9.45 1.9.46 1.9.5 1.9.50 1.9.51 1.9.60 1.9.62 1.9.63 1.9.64 1.11.12 1.4.8 1.11.15 1.4.9 1.11.17 1.5.16 1.11.18 1.5.20 1.11.2 1.5.21 1.11.20 1.5.22 1.11.23 1.5.5 1.11.24 1.5.6 1.11.25 1.5.7 1.11.26 1.5.8 1.11.27 1.5.9 1.11.28 1.6.1 1.11.3 1.6.17 1.11.4 1.6.2 1.11.5 1.6.46 1.11.8 1.7.0 1.11.9 1.7.1 1.12.0 1.7.18 1.12.1 1.7.20 1.12.12 1.7.3 1.12.13 1.7.34 1.12.15 1.7.35 1.12.17 1.7.39 1.12.2 1.7.40 1.12.20 1.7.41 1.12.23 1.8.1 1.12.24 1.8.11 1.12.25 1.8.12 1.12.28 1.8.13 1.12.29 1.8.2 1.12.30 1.8.5 1.12.32 1.8.8 1.12.34 1.9.0 1.12.35 1.9.13 1.12.37 1.9.15 1.12.39 1.9.17 1.12.4 1.12.40 1.12.6 1.13.1 1.13.11 1.13.12 1.13.15 1.13.16 1.13.2 1.13.3 1.13.4 1.13.5 1.13.6 1.13.7 1.13.8 1.13.9 1.14.10 1.14.11 1.14.12 1.14.13 1.14.2 1.14.3 1.14.4 1.14.5 1.14.7 1.14.9 1.15.0 1.15.2 1.15.3 1.15.5 1.15.6 1.15.7 1.16.0 1.16.10 1.16.11 1.16.12 1.16.13 1.16.14 1.16.15 1.16.16 1.16.17 1.16.20 1.16.21 1.16.22 1.16.23 1.16.24 1.16.25 1.16.26 1.16.28 1.16.29 1.16.32 1.16.34 1.16.35 1.16.36 1.16.37 1.16.4 1.16.40 1.16.41 1.16.42 1.16.43 1.16.44 1.16.45 1.16.46 1.16.47 1.16.48 1.16.49 1.16.5 1.16.50 1.16.51 1.16.53 1.16.55 1.16.56 1.16.59 1.16.6 1.16.60 1.16.61 1.16.62 1.16.63 1.16.64 1.16.65 1.16.66 1.16.67 1.16.68 1.16.69 1.16.7 1.16.8 1.16.9 1.2.0 1.2.1 1.2.10 1.2.11 1.2.12 1.2.14 1.2.15 1.2.16 1.2.17 1.2.19 1.2.2 1.2.20 1.2.24 1.2.25 1.2.26 1.2.27 1.2.28 1.2.29 1.2.3 1.2.30 1.2.31 1.2.33 1.2.35 1.2.36 1.2.38 1.2.39 1.2.4 1.2.40 1.2.41 1.2.42 1.2.43 1.2.44 1.2.45 1.2.46 1.2.5 1.2.7 1.2.8 1.2.9 1.22.1 1.22.10 1.22.11 1.22.12 1.22.14 1.22.15 1.22.16 1.22.17 1.22.18 1.22.19 1.22.20 1.22.21 1.22.22 1.22.23 1.22.24 1.22.3 1.22.4 1.22.5 1.22.6 1.22.7 1.22.8 1.22.9 1.23.1 1.23.10 1.23.11 1.23.12 1.23.13 1.23.15 1.23.16 1.23.2 1.23.3 1.23.4 1.23.5 1.23.6 1.23.7 1.23.8 1.23.9 1.24.1 1.24.10 1.24.11 1.24.12 1.24.2 trunk 1.24.3 0.7.4 1.24.4 0.7.7 1.24.5 0.8.28 1.24.6 0.8.29 1.24.7 0.8.30 1.24.8 0.8.31 1.24.9 0.8.32 1.25.1 0.8.33 1.25.2 0.8.36 1.25.3 0.8.37 1.25.5 0.8.50 1.25.6 0.8.51 1.25.7 0.9.1 1.25.8 0.9.10 1.25.9 0.9.11 1.26.1 0.9.12 1.26.2 0.9.2 1.3.10 0.9.20 1.3.12 0.9.21 1.3.14 0.9.22 1.3.15 1.0.10 1.3.17 1.0.11 1.3.18 1.0.12 1.3.19 1.0.15 1.3.2 1.0.16 1.3.20 1.0.18 1.3.22 1.0.20 1.3.23 1.0.3 1.3.24 1.0.4 1.3.25 1.0.5 1.3.3 1.0.6 1.3.4 1.0.7 1.3.6 1.0.8 1.3.7 1.0.9 1.3.8 1.1.0 1.3.9 1.1.10 1.4.0 1.1.11 1.4.10 1.1.12 1.4.11 1.1.13 1.4.12 1.1.14 1.4.13 1.1.15 1.4.14 1.1.16 1.4.15 1.1.17 1.4.2 1.1.2 1.4.27 1.1.3 1.4.28 1.1.5 1.4.29 1.1.6 1.4.30 1.1.8 1.4.4 1.1.9 1.4.48 1.10.1 1.4.5 1.10.3 1.4.6 1.11.1 1.4.7
updraftplus / includes / S3.php
updraftplus / includes Last commit date
Dropbox 11 years ago Google 11 years ago cloudfiles 12 years ago images 12 years ago phpseclib 11 years ago S3.php 11 years ago cacert.pem 11 years ago class-semaphore.php 12 years ago ftp.class.php 11 years ago get-cpanel-quota-usage.pl 12 years ago jquery-ui-1.8.22.custom.css 11 years ago updraft-admin-ui.js 11 years ago
S3.php
2250 lines
1 <?php
2 /**
3 * $Id$
4 *
5 * Copyright (c) 2011, Donovan Schönknecht. All rights reserved.
6 * Portions copyright (c) 2012-3, David Anderson (http://www.simbahosting.co.uk). All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions are met:
10 *
11 * - Redistributions of source code must retain the above copyright notice,
12 * this list of conditions and the following disclaimer.
13 * - Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 * POSSIBILITY OF SUCH DAMAGE.
28 *
29 * Amazon S3 is a trademark of Amazon.com, Inc. or its affiliates.
30 */
31
32 /**
33 * Amazon S3 PHP class
34 *
35 * @link http://undesigned.org.za/2007/10/22/amazon-s3-php-class
36 * @version 0.5.0-dev
37 */
38 class UpdraftPlus_S3
39 {
40 // ACL flags
41 const ACL_PRIVATE = 'private';
42 const ACL_PUBLIC_READ = 'public-read';
43 const ACL_PUBLIC_READ_WRITE = 'public-read-write';
44 const ACL_AUTHENTICATED_READ = 'authenticated-read';
45
46 const STORAGE_CLASS_STANDARD = 'STANDARD';
47 const STORAGE_CLASS_RRS = 'REDUCED_REDUNDANCY';
48
49 private static $__accessKey = null; // AWS Access key
50 private static $__secretKey = null; // AWS Secret key
51 private static $__sslKey = null;
52
53 public static $endpoint = 's3.amazonaws.com';
54 public static $proxy = null;
55
56 // Added to cope with a particular situation where the user had no pernmission to check the bucket location, which necessitated using DNS-based endpoints.
57 public static $use_dns_bucket_name = false;
58
59 public static $useSSL = false;
60 public static $useSSLValidation = true;
61 public static $useExceptions = false;
62
63 // SSL CURL SSL options - only needed if you are experiencing problems with your OpenSSL configuration
64 public static $sslKey = null;
65 public static $sslCert = null;
66 public static $sslCACert = null;
67
68 private static $__signingKeyPairId = null; // AWS Key Pair ID
69 private static $__signingKeyResource = false; // Key resource, freeSigningKey() must be called to clear it from memory
70
71
72 /**
73 * Constructor - if you're not using the class statically
74 *
75 * @param string $accessKey Access key
76 * @param string $secretKey Secret key
77 * @param boolean $useSSL Enable SSL
78 * @return void
79 */
80 public function __construct($accessKey = null, $secretKey = null, $useSSL = false, $endpoint = 's3.amazonaws.com')
81 {
82 if ($accessKey !== null && $secretKey !== null)
83 self::setAuth($accessKey, $secretKey);
84 self::$useSSL = $useSSL;
85 self::$endpoint = $endpoint;
86 }
87
88
89 /**
90 * Set the sertvice endpoint
91 *
92 * @param string $host Hostname
93 * @return void
94 */
95 public function setEndpoint($host)
96 {
97 self::$endpoint = $host;
98 }
99
100 /**
101 * Set AWS access key and secret key
102 *
103 * @param string $accessKey Access key
104 * @param string $secretKey Secret key
105 * @return void
106 */
107 public static function setAuth($accessKey, $secretKey)
108 {
109 self::$__accessKey = $accessKey;
110 self::$__secretKey = $secretKey;
111 }
112
113
114 /**
115 * Check if AWS keys have been set
116 *
117 * @return boolean
118 */
119 public static function hasAuth() {
120 return (self::$__accessKey !== null && self::$__secretKey !== null);
121 }
122
123
124 /**
125 * Set SSL on or off
126 *
127 * @param boolean $enabled SSL enabled
128 * @param boolean $validate SSL certificate validation
129 * @return void
130 */
131 public static function setSSL($enabled, $validate = true)
132 {
133 self::$useSSL = $enabled;
134 self::$useSSLValidation = $validate;
135 }
136
137 public static function getuseSSL() {
138 return self::$useSSL;
139 }
140
141 /**
142 * Set SSL client certificates (experimental)
143 *
144 * @param string $sslCert SSL client certificate
145 * @param string $sslKey SSL client key
146 * @param string $sslCACert SSL CA cert (only required if you are having problems with your system CA cert)
147 * @return void
148 */
149 public static function setSSLAuth($sslCert = null, $sslKey = null, $sslCACert = null)
150 {
151 self::$sslCert = $sslCert;
152 self::$sslKey = $sslKey;
153 self::$sslCACert = $sslCACert;
154 }
155
156
157 /**
158 * Set proxy information
159 *
160 * @param string $host Proxy hostname and port (localhost:1234)
161 * @param string $user Proxy username
162 * @param string $pass Proxy password
163 * @param constant $type CURL proxy type
164 * @return void
165 */
166 public static function setProxy($host, $user = null, $pass = null, $type = CURLPROXY_SOCKS5, $port = null)
167 {
168 self::$proxy = array('host' => $host, 'type' => $type, 'user' => $user, 'pass' => $pass, 'port' => $port);
169 }
170
171
172 /**
173 * Set the error mode to exceptions
174 *
175 * @param boolean $enabled Enable exceptions
176 * @return void
177 */
178 public static function setExceptions($enabled = true)
179 {
180 self::$useExceptions = $enabled;
181 }
182
183
184 /**
185 * Set signing key
186 *
187 * @param string $keyPairId AWS Key Pair ID
188 * @param string $signingKey Private Key
189 * @param boolean $isFile Load private key from file, set to false to load string
190 * @return boolean
191 */
192 public static function setSigningKey($keyPairId, $signingKey, $isFile = true)
193 {
194 self::$__signingKeyPairId = $keyPairId;
195 if ((self::$__signingKeyResource = openssl_pkey_get_private($isFile ?
196 file_get_contents($signingKey) : $signingKey)) !== false) return true;
197 self::__triggerError('UpdraftPlus_S3::setSigningKey(): Unable to open load private key: '.$signingKey, __FILE__, __LINE__);
198 return false;
199 }
200
201
202 /**
203 * Free signing key from memory, MUST be called if you are using setSigningKey()
204 *
205 * @return void
206 */
207 public static function freeSigningKey()
208 {
209 if (self::$__signingKeyResource !== false)
210 openssl_free_key(self::$__signingKeyResource);
211 }
212
213
214 /**
215 * Internal error handler
216 *
217 * @internal Internal error handler
218 * @param string $message Error message
219 * @param string $file Filename
220 * @param integer $line Line number
221 * @param integer $code Error code
222 * @return void
223 */
224 private static function __triggerError($message, $file, $line, $code = 0)
225 {
226 if (self::$useExceptions)
227 throw new UpdraftPlus_S3Exception($message, $file, $line, $code);
228 else
229 trigger_error($message, E_USER_WARNING);
230 }
231
232
233 /**
234 * Get a list of buckets
235 *
236 * @param boolean $detailed Returns detailed bucket list when true
237 * @return array | false
238 */
239 public static function listBuckets($detailed = false)
240 {
241 $rest = new UpdraftPlus_S3Request('GET', '', '', self::$endpoint, self::$use_dns_bucket_name);
242 $rest = $rest->getResponse();
243 if ($rest->error === false && $rest->code !== 200)
244 $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
245 if ($rest->error !== false)
246 {
247 self::__triggerError(sprintf("UpdraftPlus_S3::listBuckets(): [%s] %s", $rest->error['code'],
248 $rest->error['message']), __FILE__, __LINE__);
249 return false;
250 }
251 $results = array();
252 if (!isset($rest->body->Buckets)) return $results;
253
254 if ($detailed)
255 {
256 if (isset($rest->body->Owner, $rest->body->Owner->ID, $rest->body->Owner->DisplayName))
257 $results['owner'] = array(
258 'id' => (string)$rest->body->Owner->ID, 'name' => (string)$rest->body->Owner->ID
259 );
260 $results['buckets'] = array();
261 foreach ($rest->body->Buckets->Bucket as $b)
262 $results['buckets'][] = array(
263 'name' => (string)$b->Name, 'time' => strtotime((string)$b->CreationDate)
264 );
265 } else
266 foreach ($rest->body->Buckets->Bucket as $b) $results[] = (string)$b->Name;
267
268 return $results;
269 }
270
271 public static function useDNSBucketName($use = true) {
272 self::$use_dns_bucket_name = $use;
273 return true;
274 }
275
276 /*
277 * Get contents for a bucket
278 *
279 * If maxKeys is null this method will loop through truncated result sets
280 *
281 * @param string $bucket Bucket name
282 * @param string $prefix Prefix
283 * @param string $marker Marker (last file listed)
284 * @param string $maxKeys Max keys (maximum number of keys to return)
285 * @param string $delimiter Delimiter
286 * @param boolean $returnCommonPrefixes Set to true to return CommonPrefixes
287 * @return array | false
288 */
289 public static function getBucket($bucket, $prefix = null, $marker = null, $maxKeys = null, $delimiter = null, $returnCommonPrefixes = false)
290 {
291 $rest = new UpdraftPlus_S3Request('GET', $bucket, '', self::$endpoint, self::$use_dns_bucket_name);
292 if ($maxKeys == 0) $maxKeys = null;
293 if ($prefix !== null && $prefix !== '') $rest->setParameter('prefix', $prefix);
294 if ($marker !== null && $marker !== '') $rest->setParameter('marker', $marker);
295 if ($maxKeys !== null && $maxKeys !== '') $rest->setParameter('max-keys', $maxKeys);
296 if ($delimiter !== null && $delimiter !== '') $rest->setParameter('delimiter', $delimiter);
297 $response = $rest->getResponse();
298 if ($response->error === false && $response->code !== 200)
299 $response->error = array('code' => $response->code, 'message' => 'Unexpected HTTP status');
300 if ($response->error !== false)
301 {
302 self::__triggerError(sprintf("UpdraftPlus_S3::getBucket(): [%s] %s",
303 $response->error['code'], $response->error['message']), __FILE__, __LINE__);
304 return false;
305 }
306
307 $results = array();
308
309 $nextMarker = null;
310 if (isset($response->body, $response->body->Contents))
311 foreach ($response->body->Contents as $c)
312 {
313 $results[(string)$c->Key] = array(
314 'name' => (string)$c->Key,
315 'time' => strtotime((string)$c->LastModified),
316 'size' => (int)$c->Size,
317 'hash' => substr((string)$c->ETag, 1, -1)
318 );
319 $nextMarker = (string)$c->Key;
320 }
321
322 if ($returnCommonPrefixes && isset($response->body, $response->body->CommonPrefixes))
323 foreach ($response->body->CommonPrefixes as $c)
324 $results[(string)$c->Prefix] = array('prefix' => (string)$c->Prefix);
325
326 if (isset($response->body, $response->body->IsTruncated) &&
327 (string)$response->body->IsTruncated == 'false') return $results;
328
329 if (isset($response->body, $response->body->NextMarker))
330 $nextMarker = (string)$response->body->NextMarker;
331
332 // Loop through truncated results if maxKeys isn't specified
333 if ($maxKeys == null && $nextMarker !== null && (string)$response->body->IsTruncated == 'true')
334 do
335 {
336 $rest = new UpdraftPlus_S3Request('GET', $bucket, '', self::$endpoint, self::$use_dns_bucket_name);
337 if ($prefix !== null && $prefix !== '') $rest->setParameter('prefix', $prefix);
338 $rest->setParameter('marker', $nextMarker);
339 if ($delimiter !== null && $delimiter !== '') $rest->setParameter('delimiter', $delimiter);
340
341 if (($response = $rest->getResponse()) == false || $response->code !== 200) break;
342
343 if (isset($response->body, $response->body->Contents))
344 foreach ($response->body->Contents as $c)
345 {
346 $results[(string)$c->Key] = array(
347 'name' => (string)$c->Key,
348 'time' => strtotime((string)$c->LastModified),
349 'size' => (int)$c->Size,
350 'hash' => substr((string)$c->ETag, 1, -1)
351 );
352 $nextMarker = (string)$c->Key;
353 }
354
355 if ($returnCommonPrefixes && isset($response->body, $response->body->CommonPrefixes))
356 foreach ($response->body->CommonPrefixes as $c)
357 $results[(string)$c->Prefix] = array('prefix' => (string)$c->Prefix);
358
359 if (isset($response->body, $response->body->NextMarker))
360 $nextMarker = (string)$response->body->NextMarker;
361
362 } while ($response !== false && (string)$response->body->IsTruncated == 'true');
363
364 return $results;
365 }
366
367
368 /**
369 * Put a bucket
370 *
371 * @param string $bucket Bucket name
372 * @param constant $acl ACL flag
373 * @param string $location Set as "EU" to create buckets hosted in Europe
374 * @return boolean
375 */
376 public static function putBucket($bucket, $acl = self::ACL_PRIVATE, $location = false)
377 {
378 $rest = new UpdraftPlus_S3Request('PUT', $bucket, '', self::$endpoint, self::$use_dns_bucket_name);
379 $rest->setAmzHeader('x-amz-acl', $acl);
380
381 if ($location !== false)
382 {
383 $dom = new DOMDocument;
384 $createBucketConfiguration = $dom->createElement('CreateBucketConfiguration');
385 $locationConstraint = $dom->createElement('LocationConstraint', $location);
386 $createBucketConfiguration->appendChild($locationConstraint);
387 $dom->appendChild($createBucketConfiguration);
388 $rest->data = $dom->saveXML();
389 $rest->size = strlen($rest->data);
390 $rest->setHeader('Content-Type', 'application/xml');
391 }
392 $rest = $rest->getResponse();
393
394 if ($rest->error === false && $rest->code !== 200)
395 $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
396 if ($rest->error !== false)
397 {
398 self::__triggerError(sprintf("UpdraftPlus_S3::putBucket({$bucket}, {$acl}, {$location}): [%s] %s",
399 $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
400 return false;
401 }
402 return true;
403 }
404
405
406 /**
407 * Delete an empty bucket
408 *
409 * @param string $bucket Bucket name
410 * @return boolean
411 */
412 public static function deleteBucket($bucket)
413 {
414 $rest = new UpdraftPlus_S3Request('DELETE', $bucket, '', self::$endpoint, self::$use_dns_bucket_name);
415 $rest = $rest->getResponse();
416 if ($rest->error === false && $rest->code !== 204)
417 $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
418 if ($rest->error !== false)
419 {
420 self::__triggerError(sprintf("UpdraftPlus_S3::deleteBucket({$bucket}): [%s] %s",
421 $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
422 return false;
423 }
424 return true;
425 }
426
427
428 /**
429 * Create input info array for putObject()
430 *
431 * @param string $file Input file
432 * @param mixed $md5sum Use MD5 hash (supply a string if you want to use your own)
433 * @return array | false
434 */
435 public static function inputFile($file, $md5sum = true)
436 {
437 if (!file_exists($file) || !is_file($file) || !is_readable($file))
438 {
439 self::__triggerError('UpdraftPlus_S3::inputFile(): Unable to open input file: '.$file, __FILE__, __LINE__);
440 return false;
441 }
442 return array('file' => $file, 'size' => filesize($file), 'md5sum' => $md5sum !== false ?
443 (is_string($md5sum) ? $md5sum : base64_encode(md5_file($file, true))) : '');
444 }
445
446
447 /**
448 * Create input array info for putObject() with a resource
449 *
450 * @param string $resource Input resource to read from
451 * @param integer $bufferSize Input byte size
452 * @param string $md5sum MD5 hash to send (optional)
453 * @return array | false
454 */
455 public static function inputResource(&$resource, $bufferSize, $md5sum = '')
456 {
457 if (!is_resource($resource) || $bufferSize < 0)
458 {
459 self::__triggerError('UpdraftPlus_S3::inputResource(): Invalid resource or buffer size', __FILE__, __LINE__);
460 return false;
461 }
462 $input = array('size' => $bufferSize, 'md5sum' => $md5sum);
463 $input['fp'] =& $resource;
464 return $input;
465 }
466
467 /**
468 * Initiate a multi-part upload (http://docs.amazonwebservices.com/AmazonS3/latest/API/mpUploadInitiate.html)
469 *
470 * @param string $bucket Bucket name
471 * @param string $uri Object URI
472 * @param constant $acl ACL constant
473 * @param array $metaHeaders Array of x-amz-meta-* headers
474 * @param array $requestHeaders Array of request headers or content type as a string
475 * @param constant $storageClass Storage class constant
476 * @return string | false
477 */
478
479 public static function initiateMultipartUpload ($bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD)
480 {
481
482 $rest = new UpdraftPlus_S3Request('POST', $bucket, $uri, self::$endpoint, self::$use_dns_bucket_name);
483 $rest->setParameter('uploads','');
484
485 // Custom request headers (Content-Type, Content-Disposition, Content-Encoding)
486 if (is_array($requestHeaders))
487 foreach ($requestHeaders as $h => $v) $rest->setHeader($h, $v);
488
489 // Set storage class
490 if ($storageClass !== self::STORAGE_CLASS_STANDARD) // Storage class
491 $rest->setAmzHeader('x-amz-storage-class', $storageClass);
492
493 // Set ACL headers
494 $rest->setAmzHeader('x-amz-acl', $acl);
495 foreach ($metaHeaders as $h => $v) $rest->setAmzHeader('x-amz-meta-'.$h, $v);
496
497 // Carry out the HTTP operation
498 $rest->getResponse();
499
500 if ($rest->response->error === false && $rest->response->code !== 200)
501 $rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status');
502 if ($rest->response->error !== false)
503 {
504 self::__triggerError(sprintf("UpdraftPlus_S3::initiateMultipartUpload(): [%s] %s",
505 $rest->response->error['code'], $rest->response->error['message']), __FILE__, __LINE__);
506 return false;
507 } elseif (isset($rest->response->body))
508 {
509 // DreamObjects already returns a SimpleXMLElement here. Not sure how that works.
510 if (is_a($rest->response->body, 'SimpleXMLElement')) {
511 $body = $rest->response->body;
512 } else {
513 $body = new SimpleXMLElement($rest->response->body);
514 }
515 return (string) $body->UploadId;
516 }
517
518 // It is a programming error if we reach this line
519 return false;
520
521 }
522
523 /**
524 /* Upload a part of a multi-part set (http://docs.amazonwebservices.com/AmazonS3/latest/API/mpUploadUploadPart.html)
525 * The chunk is read into memory, so make sure that you have enough (or patch this function to work another way!)
526 *
527 * @param string $bucket Bucket name
528 * @param string $uri Object URI
529 * @param string $uploadId uploadId returned previously from initiateMultipartUpload
530 * @param integer $partNumber sequential part number to upload
531 * @param string $filePath file to upload content from
532 * @param integer $partSize number of bytes in each part (though final part may have fewer) - pass the same value each time (for this particular upload) - default 5Mb (which is Amazon's minimum)
533 * @return string (ETag) | false
534 */
535
536 public static function uploadPart ($bucket, $uri, $uploadId, $filePath, $partNumber, $partSize = 5242880)
537 {
538
539 $rest = new UpdraftPlus_S3Request('PUT', $bucket, $uri, self::$endpoint, self::$use_dns_bucket_name);
540 $rest->setParameter('partNumber', $partNumber);
541 $rest->setParameter('uploadId', $uploadId);
542
543 // Where to begin
544 $fileOffset = ($partNumber - 1 ) * $partSize;
545
546 // Download the smallest of the remaining bytes and the part size
547 $fileBytes = min(filesize($filePath) - $fileOffset, $partSize);
548 if ($fileBytes < 0) $fileBytes = 0;
549
550 $rest->setHeader('Content-Type', 'application/octet-stream');
551 $rest->data = "";
552
553 if ($handle = fopen($filePath, "rb")) {
554 if ($fileOffset >0) fseek($handle, $fileOffset);
555 $bytes_read = 0;
556 while ($fileBytes>0 && $read = fread($handle, max($fileBytes, 65536))) {
557 $fileBytes = $fileBytes - strlen($read);
558 $bytes_read += strlen($read);
559 $rest->data = $rest->data . $read;
560 }
561 fclose($handle);
562 } else {
563 return false;
564 }
565
566 $rest->setHeader('Content-MD5', base64_encode(md5($rest->data, true)));
567 $rest->size = $bytes_read;
568
569 $rest = $rest->getResponse();
570 if ($rest->error === false && $rest->code !== 200)
571 $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
572 if ($rest->error !== false)
573 {
574 self::__triggerError(sprintf("UpdraftPlus_S3::uploadPart(): [%s] %s",
575 $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
576 return false;
577 }
578 return $rest->headers['hash'];
579
580 }
581
582 /**
583 * Complete a multi-part upload (http://docs.amazonwebservices.com/AmazonS3/latest/API/mpUploadComplete.html)
584 *
585 * @param string $bucket Bucket name
586 * @param string $uri Object URI
587 * @param string $uploadId uploadId returned previously from initiateMultipartUpload
588 * @param array $parts an ordered list of eTags of previously uploaded parts from uploadPart
589 * @return boolean
590 */
591
592 public static function completeMultipartUpload ($bucket, $uri, $uploadId, $parts)
593 {
594 $rest = new UpdraftPlus_S3Request('POST', $bucket, $uri, self::$endpoint, self::$use_dns_bucket_name);
595 $rest->setParameter('uploadId', $uploadId);
596
597 $xml = "<CompleteMultipartUpload>\n";
598 $partno = 1;
599 foreach ($parts as $etag) {
600 $xml .= "<Part><PartNumber>$partno</PartNumber><ETag>$etag</ETag></Part>\n";
601 $partno++;
602 }
603 $xml .= "</CompleteMultipartUpload>";
604
605 $rest->data = $xml;
606 $rest->size = strlen($rest->data);
607 $rest->setHeader('Content-Type', 'application/xml');
608
609 $rest = $rest->getResponse();
610 if ($rest->error === false && $rest->code !== 200)
611 $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
612 if ($rest->error !== false)
613 {
614 self::__triggerError(sprintf("UpdraftPlus_S3::completeMultipartUpload(): [%s] %s",
615 $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
616 return false;
617 }
618 return true;
619
620 }
621
622 /**
623 * Abort a multi-part upload (http://docs.amazonwebservices.com/AmazonS3/latest/API/mpUploadAbort.html)
624 *
625 * @param string $bucket Bucket name
626 * @param string $uri Object URI
627 * @param string $uploadId uploadId returned previously from initiateMultipartUpload
628 * @return boolean
629 */
630
631 public static function abortMultipartUpload ($bucket, $uri, $uploadId)
632 {
633 $rest = new UpdraftPlus_S3Request('DELETE', $bucket, $uri, self::$endpoint, self::$use_dns_bucket_name);
634 $rest->setParameter('uploadId', $uploadId);
635 $rest = $rest->getResponse();
636 if ($rest->error === false && $rest->code !== 204)
637 $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
638 if ($rest->error !== false)
639 {
640 self::__triggerError(sprintf("UpdraftPlus_S3::abortMultipartUpload(): [%s] %s",
641 $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
642 return false;
643 }
644 return true;
645 }
646
647 /**
648 * Put an object
649 *
650 * @param mixed $input Input data
651 * @param string $bucket Bucket name
652 * @param string $uri Object URI
653 * @param constant $acl ACL constant
654 * @param array $metaHeaders Array of x-amz-meta-* headers
655 * @param array $requestHeaders Array of request headers or content type as a string
656 * @param constant $storageClass Storage class constant
657 * @return boolean
658 */
659 public static function putObject($input, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD)
660 {
661 if ($input === false) return false;
662 $rest = new UpdraftPlus_S3Request('PUT', $bucket, $uri, self::$endpoint, self::$use_dns_bucket_name);
663
664 if (!is_array($input)) $input = array(
665 'data' => $input, 'size' => strlen($input),
666 'md5sum' => base64_encode(md5($input, true))
667 );
668
669 // Data
670 if (isset($input['fp']))
671 $rest->fp =& $input['fp'];
672 elseif (isset($input['file']) && is_file($input['file']))
673 $rest->fp = @fopen($input['file'], 'rb');
674 elseif (isset($input['data']))
675 $rest->data = $input['data'];
676
677 // Content-Length (required)
678 if (isset($input['size']) && $input['size'] >= 0)
679 $rest->size = $input['size'];
680 else {
681 if (isset($input['file']))
682 $rest->size = filesize($input['file']);
683 elseif (isset($input['data']))
684 $rest->size = strlen($input['data']);
685 }
686
687 // Custom request headers (Content-Type, Content-Disposition, Content-Encoding)
688 if (is_array($requestHeaders))
689 foreach ($requestHeaders as $h => $v) $rest->setHeader($h, $v);
690 elseif (is_string($requestHeaders)) // Support for legacy contentType parameter
691 $input['type'] = $requestHeaders;
692
693 // Content-Type
694 if (!isset($input['type']))
695 {
696 if (isset($requestHeaders['Content-Type']))
697 $input['type'] =& $requestHeaders['Content-Type'];
698 elseif (isset($input['file']))
699 $input['type'] = self::__getMimeType($input['file']);
700 else
701 $input['type'] = 'application/octet-stream';
702 }
703
704 if ($storageClass !== self::STORAGE_CLASS_STANDARD) // Storage class
705 $rest->setAmzHeader('x-amz-storage-class', $storageClass);
706
707 // We need to post with Content-Length and Content-Type, MD5 is optional
708 if ($rest->size >= 0 && ($rest->fp !== false || $rest->data !== false))
709 {
710 $rest->setHeader('Content-Type', $input['type']);
711 if (isset($input['md5sum'])) $rest->setHeader('Content-MD5', $input['md5sum']);
712
713 $rest->setAmzHeader('x-amz-acl', $acl);
714 foreach ($metaHeaders as $h => $v) $rest->setAmzHeader('x-amz-meta-'.$h, $v);
715 $rest->getResponse();
716 } else
717 $rest->response->error = array('code' => 0, 'message' => 'Missing input parameters');
718
719 if ($rest->response->error === false && $rest->response->code !== 200)
720 $rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status');
721 if ($rest->response->error !== false)
722 {
723 self::__triggerError(sprintf("UpdraftPlus_S3::putObject(): [%s] %s",
724 $rest->response->error['code'], $rest->response->error['message']), __FILE__, __LINE__);
725 return false;
726 }
727 return true;
728 }
729
730
731 /**
732 * Put an object from a file (legacy function)
733 *
734 * @param string $file Input file path
735 * @param string $bucket Bucket name
736 * @param string $uri Object URI
737 * @param constant $acl ACL constant
738 * @param array $metaHeaders Array of x-amz-meta-* headers
739 * @param string $contentType Content type
740 * @return boolean
741 */
742 public static function putObjectFile($file, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = null, $storageClass = self::STORAGE_CLASS_STANDARD)
743 {
744 return self::putObject(self::inputFile($file), $bucket, $uri, $acl, $metaHeaders, $contentType, $storageClass);
745 }
746
747
748 /**
749 * Put an object from a string (legacy function)
750 *
751 * @param string $string Input data
752 * @param string $bucket Bucket name
753 * @param string $uri Object URI
754 * @param constant $acl ACL constant
755 * @param array $metaHeaders Array of x-amz-meta-* headers
756 * @param string $contentType Content type
757 * @return boolean
758 */
759 public static function putObjectString($string, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = 'text/plain')
760 {
761 return self::putObject($string, $bucket, $uri, $acl, $metaHeaders, $contentType);
762 }
763
764
765 /**
766 * Get an object
767 *
768 * @param string $bucket Bucket name
769 * @param string $uri Object URI
770 * @param mixed $saveTo Filename or resource to write to
771 * @param boolean resume, if possible
772 * @return mixed
773 */
774 public static function getObject($bucket, $uri, $saveTo = false, $resume = false)
775 {
776 $rest = new UpdraftPlus_S3Request('GET', $bucket, $uri, self::$endpoint, self::$use_dns_bucket_name);
777 if ($saveTo !== false)
778 {
779 if (is_resource($saveTo))
780 $rest->fp =& $saveTo;
781 else
782 if ($resume && file_exists($saveTo)) {
783 if (($rest->fp = @fopen($saveTo, 'ab')) !== false) {
784 $rest->setHeader('Range', "bytes=".filesize($saveTo).'-');
785 $rest->file = realpath($saveTo);
786 } else {
787 $rest->response->error = array('code' => 0, 'message' => 'Unable to open save file for writing: '.$saveTo);
788 }
789 } else {
790 if (($rest->fp = @fopen($saveTo, 'wb')) !== false)
791 $rest->file = realpath($saveTo);
792 else
793 $rest->response->error = array('code' => 0, 'message' => 'Unable to open save file for writing: '.$saveTo);
794 }
795 }
796 if ($rest->response->error === false) $rest->getResponse();
797
798 if ($rest->response->error === false && ( !$resume && $rest->response->code != 200) || ( $resume && $rest->response->code != 206 && $rest->response->code != 200))
799 $rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status');
800 if ($rest->response->error !== false)
801 {
802 self::__triggerError(sprintf("UpdraftPlus_S3::getObject({$bucket}, {$uri}): [%s] %s",
803 $rest->response->error['code'], $rest->response->error['message']), __FILE__, __LINE__);
804 return false;
805 }
806 return $rest->response;
807 }
808
809
810 /**
811 * Get object information
812 *
813 * @param string $bucket Bucket name
814 * @param string $uri Object URI
815 * @param boolean $returnInfo Return response information
816 * @return mixed | false
817 */
818 public static function getObjectInfo($bucket, $uri, $returnInfo = true)
819 {
820 $rest = new UpdraftPlus_S3Request('HEAD', $bucket, $uri, self::$endpoint, self::$use_dns_bucket_name);
821 $rest = $rest->getResponse();
822 if ($rest->error === false && ($rest->code !== 200 && $rest->code !== 404))
823 $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
824 if ($rest->error !== false)
825 {
826 self::__triggerError(sprintf("UpdraftPlus_S3::getObjectInfo({$bucket}, {$uri}): [%s] %s",
827 $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
828 return false;
829 }
830 return $rest->code == 200 ? $returnInfo ? $rest->headers : true : false;
831 }
832
833
834 /**
835 * Copy an object
836 *
837 * @param string $bucket Source bucket name
838 * @param string $uri Source object URI
839 * @param string $bucket Destination bucket name
840 * @param string $uri Destination object URI
841 * @param constant $acl ACL constant
842 * @param array $metaHeaders Optional array of x-amz-meta-* headers
843 * @param array $requestHeaders Optional array of request headers (content type, disposition, etc.)
844 * @param constant $storageClass Storage class constant
845 * @return mixed | false
846 */
847 public static function copyObject($srcBucket, $srcUri, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD)
848 {
849 $rest = new UpdraftPlus_S3Request('PUT', $bucket, $uri, self::$endpoint, self::$use_dns_bucket_name);
850 $rest->setHeader('Content-Length', 0);
851 foreach ($requestHeaders as $h => $v) $rest->setHeader($h, $v);
852 foreach ($metaHeaders as $h => $v) $rest->setAmzHeader('x-amz-meta-'.$h, $v);
853 if ($storageClass !== self::STORAGE_CLASS_STANDARD) // Storage class
854 $rest->setAmzHeader('x-amz-storage-class', $storageClass);
855 $rest->setAmzHeader('x-amz-acl', $acl);
856 $rest->setAmzHeader('x-amz-copy-source', sprintf('/%s/%s', $srcBucket, rawurlencode($srcUri)));
857 if (sizeof($requestHeaders) > 0 || sizeof($metaHeaders) > 0)
858 $rest->setAmzHeader('x-amz-metadata-directive', 'REPLACE');
859
860 $rest = $rest->getResponse();
861 if ($rest->error === false && $rest->code !== 200)
862 $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
863 if ($rest->error !== false)
864 {
865 self::__triggerError(sprintf("UpdraftPlus_S3::copyObject({$srcBucket}, {$srcUri}, {$bucket}, {$uri}): [%s] %s",
866 $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
867 return false;
868 }
869 return isset($rest->body->LastModified, $rest->body->ETag) ? array(
870 'time' => strtotime((string)$rest->body->LastModified),
871 'hash' => substr((string)$rest->body->ETag, 1, -1)
872 ) : false;
873 }
874
875
876 /**
877 * Set logging for a bucket
878 *
879 * @param string $bucket Bucket name
880 * @param string $targetBucket Target bucket (where logs are stored)
881 * @param string $targetPrefix Log prefix (e,g; domain.com-)
882 * @return boolean
883 */
884 public static function setBucketLogging($bucket, $targetBucket, $targetPrefix = null)
885 {
886 // The S3 log delivery group has to be added to the target bucket's ACP
887 if ($targetBucket !== null && ($acp = self::getAccessControlPolicy($targetBucket, '')) !== false)
888 {
889 // Only add permissions to the target bucket when they do not exist
890 $aclWriteSet = false;
891 $aclReadSet = false;
892 foreach ($acp['acl'] as $acl)
893 if ($acl['type'] == 'Group' && $acl['uri'] == 'http://acs.amazonaws.com/groups/s3/LogDelivery')
894 {
895 if ($acl['permission'] == 'WRITE') $aclWriteSet = true;
896 elseif ($acl['permission'] == 'READ_ACP') $aclReadSet = true;
897 }
898 if (!$aclWriteSet) $acp['acl'][] = array(
899 'type' => 'Group', 'uri' => 'http://acs.amazonaws.com/groups/s3/LogDelivery', 'permission' => 'WRITE'
900 );
901 if (!$aclReadSet) $acp['acl'][] = array(
902 'type' => 'Group', 'uri' => 'http://acs.amazonaws.com/groups/s3/LogDelivery', 'permission' => 'READ_ACP'
903 );
904 if (!$aclReadSet || !$aclWriteSet) self::setAccessControlPolicy($targetBucket, '', $acp);
905 }
906
907 $dom = new DOMDocument;
908 $bucketLoggingStatus = $dom->createElement('BucketLoggingStatus');
909 $bucketLoggingStatus->setAttribute('xmlns', 'http://s3.amazonaws.com/doc/2006-03-01/');
910 if ($targetBucket !== null)
911 {
912 if ($targetPrefix == null) $targetPrefix = $bucket . '-';
913 $loggingEnabled = $dom->createElement('LoggingEnabled');
914 $loggingEnabled->appendChild($dom->createElement('TargetBucket', $targetBucket));
915 $loggingEnabled->appendChild($dom->createElement('TargetPrefix', $targetPrefix));
916 // TODO: Add TargetGrants?
917 $bucketLoggingStatus->appendChild($loggingEnabled);
918 }
919 $dom->appendChild($bucketLoggingStatus);
920
921 $rest = new UpdraftPlus_S3Request('PUT', $bucket, '', self::$endpoint, self::$use_dns_bucket_name);
922 $rest->setParameter('logging', null);
923 $rest->data = $dom->saveXML();
924 $rest->size = strlen($rest->data);
925 $rest->setHeader('Content-Type', 'application/xml');
926 $rest = $rest->getResponse();
927 if ($rest->error === false && $rest->code !== 200)
928 $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
929 if ($rest->error !== false)
930 {
931 self::__triggerError(sprintf("UpdraftPlus_S3::setBucketLogging({$bucket}, {$targetBucket}): [%s] %s",
932 $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
933 return false;
934 }
935 return true;
936 }
937
938
939 /**
940 * Get logging status for a bucket
941 *
942 * This will return false if logging is not enabled.
943 * Note: To enable logging, you also need to grant write access to the log group
944 *
945 * @param string $bucket Bucket name
946 * @return array | false
947 */
948 public static function getBucketLogging($bucket)
949 {
950 $rest = new UpdraftPlus_S3Request('GET', $bucket, '', self::$endpoint, self::$use_dns_bucket_name);
951 $rest->setParameter('logging', null);
952 $rest = $rest->getResponse();
953 if ($rest->error === false && $rest->code !== 200)
954 $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
955 if ($rest->error !== false)
956 {
957 self::__triggerError(sprintf("UpdraftPlus_S3::getBucketLogging({$bucket}): [%s] %s",
958 $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
959 return false;
960 }
961 if (!isset($rest->body->LoggingEnabled)) return false; // No logging
962 return array(
963 'targetBucket' => (string)$rest->body->LoggingEnabled->TargetBucket,
964 'targetPrefix' => (string)$rest->body->LoggingEnabled->TargetPrefix,
965 );
966 }
967
968
969 /**
970 * Disable bucket logging
971 *
972 * @param string $bucket Bucket name
973 * @return boolean
974 */
975 public static function disableBucketLogging($bucket)
976 {
977 return self::setBucketLogging($bucket, null);
978 }
979
980
981 /**
982 * Get a bucket's location
983 *
984 * @param string $bucket Bucket name
985 * @return string | false
986 */
987 public static function getBucketLocation($bucket)
988 {
989
990 $rest = new UpdraftPlus_S3Request('GET', $bucket, '', self::$endpoint, self::$use_dns_bucket_name);
991 $rest->setParameter('location', null);
992 $rest = $rest->getResponse();
993 if ($rest->error === false && $rest->code !== 200)
994 $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
995 if ($rest->error !== false)
996 {
997 self::__triggerError(sprintf("UpdraftPlus_S3::getBucketLocation({$bucket}): [%s] %s",
998 $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
999 return false;
1000 }
1001 return (isset($rest->body[0]) && (string)$rest->body[0] !== '') ? (string)$rest->body[0] : 'US';
1002 }
1003
1004
1005 /**
1006 * Set object or bucket Access Control Policy
1007 *
1008 * @param string $bucket Bucket name
1009 * @param string $uri Object URI
1010 * @param array $acp Access Control Policy Data (same as the data returned from getAccessControlPolicy)
1011 * @return boolean
1012 */
1013 public static function setAccessControlPolicy($bucket, $uri = '', $acp = array())
1014 {
1015 $dom = new DOMDocument;
1016 $dom->formatOutput = true;
1017 $accessControlPolicy = $dom->createElement('AccessControlPolicy');
1018 $accessControlList = $dom->createElement('AccessControlList');
1019
1020 // It seems the owner has to be passed along too
1021 $owner = $dom->createElement('Owner');
1022 $owner->appendChild($dom->createElement('ID', $acp['owner']['id']));
1023 $owner->appendChild($dom->createElement('DisplayName', $acp['owner']['name']));
1024 $accessControlPolicy->appendChild($owner);
1025
1026 foreach ($acp['acl'] as $g)
1027 {
1028 $grant = $dom->createElement('Grant');
1029 $grantee = $dom->createElement('Grantee');
1030 $grantee->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
1031 if (isset($g['id']))
1032 { // CanonicalUser (DisplayName is omitted)
1033 $grantee->setAttribute('xsi:type', 'CanonicalUser');
1034 $grantee->appendChild($dom->createElement('ID', $g['id']));
1035 }
1036 elseif (isset($g['email']))
1037 { // AmazonCustomerByEmail
1038 $grantee->setAttribute('xsi:type', 'AmazonCustomerByEmail');
1039 $grantee->appendChild($dom->createElement('EmailAddress', $g['email']));
1040 }
1041 elseif ($g['type'] == 'Group')
1042 { // Group
1043 $grantee->setAttribute('xsi:type', 'Group');
1044 $grantee->appendChild($dom->createElement('URI', $g['uri']));
1045 }
1046 $grant->appendChild($grantee);
1047 $grant->appendChild($dom->createElement('Permission', $g['permission']));
1048 $accessControlList->appendChild($grant);
1049 }
1050
1051 $accessControlPolicy->appendChild($accessControlList);
1052 $dom->appendChild($accessControlPolicy);
1053
1054 $rest = new UpdraftPlus_S3Request('PUT', $bucket, $uri, self::$endpoint, self::$use_dns_bucket_name);
1055 $rest->setParameter('acl', null);
1056 $rest->data = $dom->saveXML();
1057 $rest->size = strlen($rest->data);
1058 $rest->setHeader('Content-Type', 'application/xml');
1059 $rest = $rest->getResponse();
1060 if ($rest->error === false && $rest->code !== 200)
1061 $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1062 if ($rest->error !== false)
1063 {
1064 self::__triggerError(sprintf("UpdraftPlus_S3::setAccessControlPolicy({$bucket}, {$uri}): [%s] %s",
1065 $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
1066 return false;
1067 }
1068 return true;
1069 }
1070
1071
1072 /**
1073 * Get object or bucket Access Control Policy
1074 *
1075 * @param string $bucket Bucket name
1076 * @param string $uri Object URI
1077 * @return mixed | false
1078 */
1079 public static function getAccessControlPolicy($bucket, $uri = '')
1080 {
1081 $rest = new UpdraftPlus_S3Request('GET', $bucket, $uri, self::$endpoint, self::$use_dns_bucket_name);
1082 $rest->setParameter('acl', null);
1083 $rest = $rest->getResponse();
1084 if ($rest->error === false && $rest->code !== 200)
1085 $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1086 if ($rest->error !== false)
1087 {
1088 self::__triggerError(sprintf("UpdraftPlus_S3::getAccessControlPolicy({$bucket}, {$uri}): [%s] %s",
1089 $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
1090 return false;
1091 }
1092
1093 $acp = array();
1094 if (isset($rest->body->Owner, $rest->body->Owner->ID, $rest->body->Owner->DisplayName))
1095 $acp['owner'] = array(
1096 'id' => (string)$rest->body->Owner->ID, 'name' => (string)$rest->body->Owner->DisplayName
1097 );
1098
1099 if (isset($rest->body->AccessControlList))
1100 {
1101 $acp['acl'] = array();
1102 foreach ($rest->body->AccessControlList->Grant as $grant)
1103 {
1104 foreach ($grant->Grantee as $grantee)
1105 {
1106 if (isset($grantee->ID, $grantee->DisplayName)) // CanonicalUser
1107 $acp['acl'][] = array(
1108 'type' => 'CanonicalUser',
1109 'id' => (string)$grantee->ID,
1110 'name' => (string)$grantee->DisplayName,
1111 'permission' => (string)$grant->Permission
1112 );
1113 elseif (isset($grantee->EmailAddress)) // AmazonCustomerByEmail
1114 $acp['acl'][] = array(
1115 'type' => 'AmazonCustomerByEmail',
1116 'email' => (string)$grantee->EmailAddress,
1117 'permission' => (string)$grant->Permission
1118 );
1119 elseif (isset($grantee->URI)) // Group
1120 $acp['acl'][] = array(
1121 'type' => 'Group',
1122 'uri' => (string)$grantee->URI,
1123 'permission' => (string)$grant->Permission
1124 );
1125 else continue;
1126 }
1127 }
1128 }
1129 return $acp;
1130 }
1131
1132
1133 /**
1134 * Delete an object
1135 *
1136 * @param string $bucket Bucket name
1137 * @param string $uri Object URI
1138 * @return boolean
1139 */
1140 public static function deleteObject($bucket, $uri)
1141 {
1142 $rest = new UpdraftPlus_S3Request('DELETE', $bucket, $uri, self::$endpoint, self::$use_dns_bucket_name);
1143 $rest = $rest->getResponse();
1144 if ($rest->error === false && $rest->code !== 204)
1145 $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1146 if ($rest->error !== false)
1147 {
1148 self::__triggerError(sprintf("UpdraftPlus_S3::deleteObject(): [%s] %s",
1149 $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
1150 return false;
1151 }
1152 return true;
1153 }
1154
1155
1156 /**
1157 * Get a query string authenticated URL
1158 *
1159 * @param string $bucket Bucket name
1160 * @param string $uri Object URI
1161 * @param integer $lifetime Lifetime in seconds
1162 * @param boolean $hostBucket Use the bucket name as the hostname
1163 * @param boolean $https Use HTTPS ($hostBucket should be false for SSL verification)
1164 * @return string
1165 */
1166 public static function getAuthenticatedURL($bucket, $uri, $lifetime, $hostBucket = false, $https = false)
1167 {
1168 $expires = time() + $lifetime;
1169 $uri = str_replace(array('%2F', '%2B'), array('/', '+'), rawurlencode($uri));
1170 return sprintf(($https ? 'https' : 'http').'://%s/%s?AWSAccessKeyId=%s&Expires=%u&Signature=%s',
1171 // $hostBucket ? $bucket : $bucket.'.s3.amazonaws.com', $uri, self::$__accessKey, $expires,
1172 $hostBucket ? $bucket : 's3.amazonaws.com/'.$bucket, $uri, self::$__accessKey, $expires,
1173 urlencode(self::__getHash("GET\n\n\n{$expires}\n/{$bucket}/{$uri}")));
1174 }
1175
1176
1177 /**
1178 * Get a CloudFront signed policy URL
1179 *
1180 * @param array $policy Policy
1181 * @return string
1182 */
1183 public static function getSignedPolicyURL($policy)
1184 {
1185 $data = json_encode($policy);
1186 $signature = '';
1187 if (!openssl_sign($data, $signature, self::$__signingKeyResource)) return false;
1188
1189 $encoded = str_replace(array('+', '='), array('-', '_', '~'), base64_encode($data));
1190 $signature = str_replace(array('+', '='), array('-', '_', '~'), base64_encode($signature));
1191
1192 $url = $policy['Statement'][0]['Resource'] . '?';
1193 foreach (array('Policy' => $encoded, 'Signature' => $signature, 'Key-Pair-Id' => self::$__signingKeyPairId) as $k => $v)
1194 $url .= $k.'='.str_replace('%2F', '/', rawurlencode($v)).'&';
1195 return substr($url, 0, -1);
1196 }
1197
1198
1199 /**
1200 * Get a CloudFront canned policy URL
1201 *
1202 * @param string $string URL to sign
1203 * @param integer $lifetime URL lifetime
1204 * @return string
1205 */
1206 public static function getSignedCannedURL($url, $lifetime)
1207 {
1208 return self::getSignedPolicyURL(array(
1209 'Statement' => array(
1210 array('Resource' => $url, 'Condition' => array(
1211 'DateLessThan' => array('AWS:EpochTime' => time() + $lifetime)
1212 ))
1213 )
1214 ));
1215 }
1216
1217
1218 /**
1219 * Get upload POST parameters for form uploads
1220 *
1221 * @param string $bucket Bucket name
1222 * @param string $uriPrefix Object URI prefix
1223 * @param constant $acl ACL constant
1224 * @param integer $lifetime Lifetime in seconds
1225 * @param integer $maxFileSize Maximum filesize in bytes (default 5MB)
1226 * @param string $successRedirect Redirect URL or 200 / 201 status code
1227 * @param array $amzHeaders Array of x-amz-meta-* headers
1228 * @param array $headers Array of request headers or content type as a string
1229 * @param boolean $flashVars Includes additional "Filename" variable posted by Flash
1230 * @return object
1231 */
1232 public static function getHttpUploadPostParams($bucket, $uriPrefix = '', $acl = self::ACL_PRIVATE, $lifetime = 3600,
1233 $maxFileSize = 5242880, $successRedirect = "201", $amzHeaders = array(), $headers = array(), $flashVars = false)
1234 {
1235 // Create policy object
1236 $policy = new stdClass;
1237 $policy->expiration = gmdate('Y-m-d\TH:i:s\Z', (time() + $lifetime));
1238 $policy->conditions = array();
1239 $obj = new stdClass; $obj->bucket = $bucket; array_push($policy->conditions, $obj);
1240 $obj = new stdClass; $obj->acl = $acl; array_push($policy->conditions, $obj);
1241
1242 $obj = new stdClass; // 200 for non-redirect uploads
1243 if (is_numeric($successRedirect) && in_array((int)$successRedirect, array(200, 201)))
1244 $obj->success_action_status = (string)$successRedirect;
1245 else // URL
1246 $obj->success_action_redirect = $successRedirect;
1247 array_push($policy->conditions, $obj);
1248
1249 if ($acl !== self::ACL_PUBLIC_READ)
1250 array_push($policy->conditions, array('eq', '$acl', $acl));
1251
1252 array_push($policy->conditions, array('starts-with', '$key', $uriPrefix));
1253 if ($flashVars) array_push($policy->conditions, array('starts-with', '$Filename', ''));
1254 foreach (array_keys($headers) as $headerKey)
1255 array_push($policy->conditions, array('starts-with', '$'.$headerKey, ''));
1256 foreach ($amzHeaders as $headerKey => $headerVal)
1257 {
1258 $obj = new stdClass;
1259 $obj->{$headerKey} = (string)$headerVal;
1260 array_push($policy->conditions, $obj);
1261 }
1262 array_push($policy->conditions, array('content-length-range', 0, $maxFileSize));
1263 $policy = base64_encode(str_replace('\/', '/', json_encode($policy)));
1264
1265 // Create parameters
1266 $params = new stdClass;
1267 $params->AWSAccessKeyId = self::$__accessKey;
1268 $params->key = $uriPrefix.'${filename}';
1269 $params->acl = $acl;
1270 $params->policy = $policy; unset($policy);
1271 $params->signature = self::__getHash($params->policy);
1272 if (is_numeric($successRedirect) && in_array((int)$successRedirect, array(200, 201)))
1273 $params->success_action_status = (string)$successRedirect;
1274 else
1275 $params->success_action_redirect = $successRedirect;
1276 foreach ($headers as $headerKey => $headerVal) $params->{$headerKey} = (string)$headerVal;
1277 foreach ($amzHeaders as $headerKey => $headerVal) $params->{$headerKey} = (string)$headerVal;
1278 return $params;
1279 }
1280
1281
1282 /**
1283 * Create a CloudFront distribution
1284 *
1285 * @param string $bucket Bucket name
1286 * @param boolean $enabled Enabled (true/false)
1287 * @param array $cnames Array containing CNAME aliases
1288 * @param string $comment Use the bucket name as the hostname
1289 * @param string $defaultRootObject Default root object
1290 * @param string $originAccessIdentity Origin access identity
1291 * @param array $trustedSigners Array of trusted signers
1292 * @return array | false
1293 */
1294 public static function createDistribution($bucket, $enabled = true, $cnames = array(), $comment = null, $defaultRootObject = null, $originAccessIdentity = null, $trustedSigners = array())
1295 {
1296 if (!extension_loaded('openssl'))
1297 {
1298 self::__triggerError(sprintf("UpdraftPlus_S3::createDistribution({$bucket}, ".(int)$enabled.", [], '$comment'): %s",
1299 "CloudFront functionality requires SSL"), __FILE__, __LINE__);
1300 return false;
1301 }
1302 $useSSL = self::$useSSL;
1303
1304 self::$useSSL = true; // CloudFront requires SSL
1305 $rest = new UpdraftPlus_S3Request('POST', '', '2010-11-01/distribution', 'cloudfront.amazonaws.com');
1306 $rest->data = self::__getCloudFrontDistributionConfigXML(
1307 $bucket.'.s3.amazonaws.com',
1308 $enabled,
1309 (string)$comment,
1310 (string)microtime(true),
1311 $cnames,
1312 $defaultRootObject,
1313 $originAccessIdentity,
1314 $trustedSigners
1315 );
1316
1317 $rest->size = strlen($rest->data);
1318 $rest->setHeader('Content-Type', 'application/xml');
1319 $rest = self::__getCloudFrontResponse($rest);
1320
1321 self::$useSSL = $useSSL;
1322
1323 if ($rest->error === false && $rest->code !== 201)
1324 $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1325 if ($rest->error !== false)
1326 {
1327 self::__triggerError(sprintf("UpdraftPlus_S3::createDistribution({$bucket}, ".(int)$enabled.", [], '$comment'): [%s] %s",
1328 $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
1329 return false;
1330 } elseif ($rest->body instanceof SimpleXMLElement)
1331 return self::__parseCloudFrontDistributionConfig($rest->body);
1332 return false;
1333 }
1334
1335
1336 /**
1337 * Get CloudFront distribution info
1338 *
1339 * @param string $distributionId Distribution ID from listDistributions()
1340 * @return array | false
1341 */
1342 public static function getDistribution($distributionId)
1343 {
1344 if (!extension_loaded('openssl'))
1345 {
1346 self::__triggerError(sprintf("UpdraftPlus_S3::getDistribution($distributionId): %s",
1347 "CloudFront functionality requires SSL"), __FILE__, __LINE__);
1348 return false;
1349 }
1350 $useSSL = self::$useSSL;
1351
1352 self::$useSSL = true; // CloudFront requires SSL
1353 $rest = new UpdraftPlus_S3Request('GET', '', '2010-11-01/distribution/'.$distributionId, 'cloudfront.amazonaws.com');
1354 $rest = self::__getCloudFrontResponse($rest);
1355
1356 self::$useSSL = $useSSL;
1357
1358 if ($rest->error === false && $rest->code !== 200)
1359 $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1360 if ($rest->error !== false)
1361 {
1362 self::__triggerError(sprintf("UpdraftPlus_S3::getDistribution($distributionId): [%s] %s",
1363 $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
1364 return false;
1365 }
1366 elseif ($rest->body instanceof SimpleXMLElement)
1367 {
1368 $dist = self::__parseCloudFrontDistributionConfig($rest->body);
1369 $dist['hash'] = $rest->headers['hash'];
1370 $dist['id'] = $distributionId;
1371 return $dist;
1372 }
1373 return false;
1374 }
1375
1376
1377 /**
1378 * Update a CloudFront distribution
1379 *
1380 * @param array $dist Distribution array info identical to output of getDistribution()
1381 * @return array | false
1382 */
1383 public static function updateDistribution($dist)
1384 {
1385 if (!extension_loaded('openssl'))
1386 {
1387 self::__triggerError(sprintf("UpdraftPlus_S3::updateDistribution({$dist['id']}): %s",
1388 "CloudFront functionality requires SSL"), __FILE__, __LINE__);
1389 return false;
1390 }
1391
1392 $useSSL = self::$useSSL;
1393
1394 self::$useSSL = true; // CloudFront requires SSL
1395 $rest = new UpdraftPlus_S3Request('PUT', '', '2010-11-01/distribution/'.$dist['id'].'/config', 'cloudfront.amazonaws.com');
1396 $rest->data = self::__getCloudFrontDistributionConfigXML(
1397 $dist['origin'],
1398 $dist['enabled'],
1399 $dist['comment'],
1400 $dist['callerReference'],
1401 $dist['cnames'],
1402 $dist['defaultRootObject'],
1403 $dist['originAccessIdentity'],
1404 $dist['trustedSigners']
1405 );
1406
1407 $rest->size = strlen($rest->data);
1408 $rest->setHeader('If-Match', $dist['hash']);
1409 $rest = self::__getCloudFrontResponse($rest);
1410
1411 self::$useSSL = $useSSL;
1412
1413 if ($rest->error === false && $rest->code !== 200)
1414 $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1415 if ($rest->error !== false)
1416 {
1417 self::__triggerError(sprintf("UpdraftPlus_S3::updateDistribution({$dist['id']}): [%s] %s",
1418 $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
1419 return false;
1420 } else {
1421 $dist = self::__parseCloudFrontDistributionConfig($rest->body);
1422 $dist['hash'] = $rest->headers['hash'];
1423 return $dist;
1424 }
1425 return false;
1426 }
1427
1428
1429 /**
1430 * Delete a CloudFront distribution
1431 *
1432 * @param array $dist Distribution array info identical to output of getDistribution()
1433 * @return boolean
1434 */
1435 public static function deleteDistribution($dist)
1436 {
1437 if (!extension_loaded('openssl'))
1438 {
1439 self::__triggerError(sprintf("UpdraftPlus_S3::deleteDistribution({$dist['id']}): %s",
1440 "CloudFront functionality requires SSL"), __FILE__, __LINE__);
1441 return false;
1442 }
1443
1444 $useSSL = self::$useSSL;
1445
1446 self::$useSSL = true; // CloudFront requires SSL
1447 $rest = new UpdraftPlus_S3Request('DELETE', '', '2008-06-30/distribution/'.$dist['id'], 'cloudfront.amazonaws.com');
1448 $rest->setHeader('If-Match', $dist['hash']);
1449 $rest = self::__getCloudFrontResponse($rest);
1450
1451 self::$useSSL = $useSSL;
1452
1453 if ($rest->error === false && $rest->code !== 204)
1454 $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1455 if ($rest->error !== false)
1456 {
1457 self::__triggerError(sprintf("UpdraftPlus_S3::deleteDistribution({$dist['id']}): [%s] %s",
1458 $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
1459 return false;
1460 }
1461 return true;
1462 }
1463
1464
1465 /**
1466 * Get a list of CloudFront distributions
1467 *
1468 * @return array
1469 */
1470 public static function listDistributions()
1471 {
1472 if (!extension_loaded('openssl'))
1473 {
1474 self::__triggerError(sprintf("UpdraftPlus_S3::listDistributions(): [%s] %s",
1475 "CloudFront functionality requires SSL"), __FILE__, __LINE__);
1476 return false;
1477 }
1478
1479 $useSSL = self::$useSSL;
1480 self::$useSSL = true; // CloudFront requires SSL
1481 $rest = new UpdraftPlus_S3Request('GET', '', '2010-11-01/distribution', 'cloudfront.amazonaws.com');
1482 $rest = self::__getCloudFrontResponse($rest);
1483 self::$useSSL = $useSSL;
1484
1485 if ($rest->error === false && $rest->code !== 200)
1486 $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1487 if ($rest->error !== false)
1488 {
1489 self::__triggerError(sprintf("UpdraftPlus_S3::listDistributions(): [%s] %s",
1490 $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
1491 return false;
1492 }
1493 elseif ($rest->body instanceof SimpleXMLElement && isset($rest->body->DistributionSummary))
1494 {
1495 $list = array();
1496 if (isset($rest->body->Marker, $rest->body->MaxItems, $rest->body->IsTruncated))
1497 {
1498 //$info['marker'] = (string)$rest->body->Marker;
1499 //$info['maxItems'] = (int)$rest->body->MaxItems;
1500 //$info['isTruncated'] = (string)$rest->body->IsTruncated == 'true' ? true : false;
1501 }
1502 foreach ($rest->body->DistributionSummary as $summary)
1503 $list[(string)$summary->Id] = self::__parseCloudFrontDistributionConfig($summary);
1504
1505 return $list;
1506 }
1507 return array();
1508 }
1509
1510 /**
1511 * List CloudFront Origin Access Identities
1512 *
1513 * @return array
1514 */
1515 public static function listOriginAccessIdentities()
1516 {
1517 if (!extension_loaded('openssl'))
1518 {
1519 self::__triggerError(sprintf("UpdraftPlus_S3::listOriginAccessIdentities(): [%s] %s",
1520 "CloudFront functionality requires SSL"), __FILE__, __LINE__);
1521 return false;
1522 }
1523
1524 self::$useSSL = true; // CloudFront requires SSL
1525 $rest = new UpdraftPlus_S3Request('GET', '', '2010-11-01/origin-access-identity/cloudfront', 'cloudfront.amazonaws.com');
1526 $rest = self::__getCloudFrontResponse($rest);
1527 $useSSL = self::$useSSL;
1528
1529 if ($rest->error === false && $rest->code !== 200)
1530 $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1531 if ($rest->error !== false)
1532 {
1533 trigger_error(sprintf("UpdraftPlus_S3::listOriginAccessIdentities(): [%s] %s",
1534 $rest->error['code'], $rest->error['message']), E_USER_WARNING);
1535 return false;
1536 }
1537
1538 if (isset($rest->body->CloudFrontOriginAccessIdentitySummary))
1539 {
1540 $identities = array();
1541 foreach ($rest->body->CloudFrontOriginAccessIdentitySummary as $identity)
1542 if (isset($identity->S3CanonicalUserId))
1543 $identities[(string)$identity->Id] = array('id' => (string)$identity->Id, 's3CanonicalUserId' => (string)$identity->S3CanonicalUserId);
1544 return $identities;
1545 }
1546 return false;
1547 }
1548
1549
1550 /**
1551 * Invalidate objects in a CloudFront distribution
1552 *
1553 * Thanks to Martin Lindkvist for UpdraftPlus_S3::invalidateDistribution()
1554 *
1555 * @param string $distributionId Distribution ID from listDistributions()
1556 * @param array $paths Array of object paths to invalidate
1557 * @return boolean
1558 */
1559 public static function invalidateDistribution($distributionId, $paths)
1560 {
1561 if (!extension_loaded('openssl'))
1562 {
1563 self::__triggerError(sprintf("UpdraftPlus_S3::invalidateDistribution(): [%s] %s",
1564 "CloudFront functionality requires SSL"), __FILE__, __LINE__);
1565 return false;
1566 }
1567
1568 $useSSL = self::$useSSL;
1569 self::$useSSL = true; // CloudFront requires SSL
1570 $rest = new UpdraftPlus_S3Request('POST', '', '2010-08-01/distribution/'.$distributionId.'/invalidation', 'cloudfront.amazonaws.com');
1571 $rest->data = self::__getCloudFrontInvalidationBatchXML($paths, (string)microtime(true));
1572 $rest->size = strlen($rest->data);
1573 $rest = self::__getCloudFrontResponse($rest);
1574 self::$useSSL = $useSSL;
1575
1576 if ($rest->error === false && $rest->code !== 201)
1577 $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1578 if ($rest->error !== false)
1579 {
1580 trigger_error(sprintf("UpdraftPlus_S3::invalidate('{$distributionId}',{$paths}): [%s] %s",
1581 $rest->error['code'], $rest->error['message']), E_USER_WARNING);
1582 return false;
1583 }
1584 return true;
1585 }
1586
1587
1588 /**
1589 * Get a InvalidationBatch DOMDocument
1590 *
1591 * @internal Used to create XML in invalidateDistribution()
1592 * @param array $paths Paths to objects to invalidateDistribution
1593 * @return string
1594 */
1595 private static function __getCloudFrontInvalidationBatchXML($paths, $callerReference = '0') {
1596 $dom = new DOMDocument('1.0', 'UTF-8');
1597 $dom->formatOutput = true;
1598 $invalidationBatch = $dom->createElement('InvalidationBatch');
1599 foreach ($paths as $path)
1600 $invalidationBatch->appendChild($dom->createElement('Path', $path));
1601
1602 $invalidationBatch->appendChild($dom->createElement('CallerReference', $callerReference));
1603 $dom->appendChild($invalidationBatch);
1604 return $dom->saveXML();
1605 }
1606
1607
1608 /**
1609 * List your invalidation batches for invalidateDistribution() in a CloudFront distribution
1610 *
1611 * http://docs.amazonwebservices.com/AmazonCloudFront/latest/APIReference/ListInvalidation.html
1612 * returned array looks like this:
1613 * Array
1614 * (
1615 * [I31TWB0CN9V6XD] => InProgress
1616 * [IT3TFE31M0IHZ] => Completed
1617 * [I12HK7MPO1UQDA] => Completed
1618 * [I1IA7R6JKTC3L2] => Completed
1619 * )
1620 *
1621 * @param string $distributionId Distribution ID from listDistributions()
1622 * @return array
1623 */
1624 public static function getDistributionInvalidationList($distributionId)
1625 {
1626 if (!extension_loaded('openssl'))
1627 {
1628 self::__triggerError(sprintf("UpdraftPlus_S3::getDistributionInvalidationList(): [%s] %s",
1629 "CloudFront functionality requires SSL"), __FILE__, __LINE__);
1630 return false;
1631 }
1632
1633 $useSSL = self::$useSSL;
1634 self::$useSSL = true; // CloudFront requires SSL
1635 $rest = new UpdraftPlus_S3Request('GET', '', '2010-11-01/distribution/'.$distributionId.'/invalidation', 'cloudfront.amazonaws.com');
1636 $rest = self::__getCloudFrontResponse($rest);
1637 self::$useSSL = $useSSL;
1638
1639 if ($rest->error === false && $rest->code !== 200)
1640 $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1641 if ($rest->error !== false)
1642 {
1643 trigger_error(sprintf("UpdraftPlus_S3::getDistributionInvalidationList('{$distributionId}'): [%s]",
1644 $rest->error['code'], $rest->error['message']), E_USER_WARNING);
1645 return false;
1646 }
1647 elseif ($rest->body instanceof SimpleXMLElement && isset($rest->body->InvalidationSummary))
1648 {
1649 $list = array();
1650 foreach ($rest->body->InvalidationSummary as $summary)
1651 $list[(string)$summary->Id] = (string)$summary->Status;
1652
1653 return $list;
1654 }
1655 return array();
1656 }
1657
1658
1659 /**
1660 * Get a DistributionConfig DOMDocument
1661 *
1662 * http://docs.amazonwebservices.com/AmazonCloudFront/latest/APIReference/index.html?PutConfig.html
1663 *
1664 * @internal Used to create XML in createDistribution() and updateDistribution()
1665 * @param string $bucket S3 Origin bucket
1666 * @param boolean $enabled Enabled (true/false)
1667 * @param string $comment Comment to append
1668 * @param string $callerReference Caller reference
1669 * @param array $cnames Array of CNAME aliases
1670 * @param string $defaultRootObject Default root object
1671 * @param string $originAccessIdentity Origin access identity
1672 * @param array $trustedSigners Array of trusted signers
1673 * @return string
1674 */
1675 private static function __getCloudFrontDistributionConfigXML($bucket, $enabled, $comment, $callerReference = '0', $cnames = array(), $defaultRootObject = null, $originAccessIdentity = null, $trustedSigners = array())
1676 {
1677 $dom = new DOMDocument('1.0', 'UTF-8');
1678 $dom->formatOutput = true;
1679 $distributionConfig = $dom->createElement('DistributionConfig');
1680 $distributionConfig->setAttribute('xmlns', 'http://cloudfront.amazonaws.com/doc/2010-11-01/');
1681
1682 $origin = $dom->createElement('S3Origin');
1683 $origin->appendChild($dom->createElement('DNSName', $bucket));
1684 if ($originAccessIdentity !== null) $origin->appendChild($dom->createElement('OriginAccessIdentity', $originAccessIdentity));
1685 $distributionConfig->appendChild($origin);
1686
1687 if ($defaultRootObject !== null) $distributionConfig->appendChild($dom->createElement('DefaultRootObject', $defaultRootObject));
1688
1689 $distributionConfig->appendChild($dom->createElement('CallerReference', $callerReference));
1690 foreach ($cnames as $cname)
1691 $distributionConfig->appendChild($dom->createElement('CNAME', $cname));
1692 if ($comment !== '') $distributionConfig->appendChild($dom->createElement('Comment', $comment));
1693 $distributionConfig->appendChild($dom->createElement('Enabled', $enabled ? 'true' : 'false'));
1694
1695 $trusted = $dom->createElement('TrustedSigners');
1696 foreach ($trustedSigners as $id => $type)
1697 $trusted->appendChild($id !== '' ? $dom->createElement($type, $id) : $dom->createElement($type));
1698 $distributionConfig->appendChild($trusted);
1699
1700 $dom->appendChild($distributionConfig);
1701 //var_dump($dom->saveXML());
1702 return $dom->saveXML();
1703 }
1704
1705
1706 /**
1707 * Parse a CloudFront distribution config
1708 *
1709 * See http://docs.amazonwebservices.com/AmazonCloudFront/latest/APIReference/index.html?GetDistribution.html
1710 *
1711 * @internal Used to parse the CloudFront DistributionConfig node to an array
1712 * @param object &$node DOMNode
1713 * @return array
1714 */
1715 private static function __parseCloudFrontDistributionConfig(&$node)
1716 {
1717 if (isset($node->DistributionConfig))
1718 return self::__parseCloudFrontDistributionConfig($node->DistributionConfig);
1719
1720 $dist = array();
1721 if (isset($node->Id, $node->Status, $node->LastModifiedTime, $node->DomainName))
1722 {
1723 $dist['id'] = (string)$node->Id;
1724 $dist['status'] = (string)$node->Status;
1725 $dist['time'] = strtotime((string)$node->LastModifiedTime);
1726 $dist['domain'] = (string)$node->DomainName;
1727 }
1728
1729 if (isset($node->CallerReference))
1730 $dist['callerReference'] = (string)$node->CallerReference;
1731
1732 if (isset($node->Enabled))
1733 $dist['enabled'] = (string)$node->Enabled == 'true' ? true : false;
1734
1735 if (isset($node->S3Origin))
1736 {
1737 if (isset($node->S3Origin->DNSName))
1738 $dist['origin'] = (string)$node->S3Origin->DNSName;
1739
1740 $dist['originAccessIdentity'] = isset($node->S3Origin->OriginAccessIdentity) ?
1741 (string)$node->S3Origin->OriginAccessIdentity : null;
1742 }
1743
1744 $dist['defaultRootObject'] = isset($node->DefaultRootObject) ? (string)$node->DefaultRootObject : null;
1745
1746 $dist['cnames'] = array();
1747 if (isset($node->CNAME))
1748 foreach ($node->CNAME as $cname)
1749 $dist['cnames'][(string)$cname] = (string)$cname;
1750
1751 $dist['trustedSigners'] = array();
1752 if (isset($node->TrustedSigners))
1753 foreach ($node->TrustedSigners as $signer)
1754 {
1755 if (isset($signer->Self))
1756 $dist['trustedSigners'][''] = 'Self';
1757 elseif (isset($signer->KeyPairId))
1758 $dist['trustedSigners'][(string)$signer->KeyPairId] = 'KeyPairId';
1759 elseif (isset($signer->AwsAccountNumber))
1760 $dist['trustedSigners'][(string)$signer->AwsAccountNumber] = 'AwsAccountNumber';
1761 }
1762
1763 $dist['comment'] = isset($node->Comment) ? (string)$node->Comment : null;
1764 return $dist;
1765 }
1766
1767
1768 /**
1769 * Grab CloudFront response
1770 *
1771 * @internal Used to parse the CloudFront UpdraftPlus_S3Request::getResponse() output
1772 * @param object &$rest UpdraftPlus_S3Request instance
1773 * @return object
1774 */
1775 private static function __getCloudFrontResponse(&$rest)
1776 {
1777 $rest->getResponse();
1778 if ($rest->response->error === false && isset($rest->response->body) &&
1779 is_string($rest->response->body) && substr($rest->response->body, 0, 5) == '<?xml')
1780 {
1781 $rest->response->body = simplexml_load_string($rest->response->body);
1782 // Grab CloudFront errors
1783 if (isset($rest->response->body->Error, $rest->response->body->Error->Code,
1784 $rest->response->body->Error->Message))
1785 {
1786 $rest->response->error = array(
1787 'code' => (string)$rest->response->body->Error->Code,
1788 'message' => (string)$rest->response->body->Error->Message
1789 );
1790 unset($rest->response->body);
1791 }
1792 }
1793 return $rest->response;
1794 }
1795
1796
1797 /**
1798 * Get MIME type for file
1799 *
1800 * @internal Used to get mime types
1801 * @param string &$file File path
1802 * @return string
1803 */
1804 public static function __getMimeType(&$file)
1805 {
1806 $type = false;
1807 // Fileinfo documentation says fileinfo_open() will use the
1808 // MAGIC env var for the magic file
1809 if (extension_loaded('fileinfo') && isset($_ENV['MAGIC']) &&
1810 ($finfo = finfo_open(FILEINFO_MIME, $_ENV['MAGIC'])) !== false)
1811 {
1812 if (($type = finfo_file($finfo, $file)) !== false)
1813 {
1814 // Remove the charset and grab the last content-type
1815 $type = explode(' ', str_replace('; charset=', ';charset=', $type));
1816 $type = array_pop($type);
1817 $type = explode(';', $type);
1818 $type = trim(array_shift($type));
1819 }
1820 finfo_close($finfo);
1821
1822 // If anyone is still using mime_content_type()
1823 } elseif (function_exists('mime_content_type'))
1824 $type = trim(mime_content_type($file));
1825
1826 if ($type !== false && strlen($type) > 0) return $type;
1827
1828 // Otherwise do it the old fashioned way
1829 static $exts = array(
1830 'jpg' => 'image/jpeg', 'gif' => 'image/gif', 'png' => 'image/png',
1831 'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'ico' => 'image/x-icon',
1832 'swf' => 'application/x-shockwave-flash', 'pdf' => 'application/pdf',
1833 'zip' => 'application/zip', 'gz' => 'application/x-gzip',
1834 'tar' => 'application/x-tar', 'bz' => 'application/x-bzip',
1835 'bz2' => 'application/x-bzip2', 'txt' => 'text/plain',
1836 'asc' => 'text/plain', 'htm' => 'text/html', 'html' => 'text/html',
1837 'css' => 'text/css', 'js' => 'text/javascript',
1838 'xml' => 'text/xml', 'xsl' => 'application/xsl+xml',
1839 'ogg' => 'application/ogg', 'mp3' => 'audio/mpeg', 'wav' => 'audio/x-wav',
1840 'avi' => 'video/x-msvideo', 'mpg' => 'video/mpeg', 'mpeg' => 'video/mpeg',
1841 'mov' => 'video/quicktime', 'flv' => 'video/x-flv', 'php' => 'text/x-php'
1842 );
1843 $ext = strtolower(pathInfo($file, PATHINFO_EXTENSION));
1844 return isset($exts[$ext]) ? $exts[$ext] : 'application/octet-stream';
1845 }
1846
1847
1848 /**
1849 * Generate the auth string: "AWS AccessKey:Signature"
1850 *
1851 * @internal Used by UpdraftPlus_S3Request::getResponse()
1852 * @param string $string String to sign
1853 * @return string
1854 */
1855 public static function __getSignature($string)
1856 {
1857 return 'AWS '.self::$__accessKey.':'.self::__getHash($string);
1858 }
1859
1860
1861 /**
1862 * Creates a HMAC-SHA1 hash
1863 *
1864 * This uses the hash extension if loaded
1865 *
1866 * @internal Used by __getSignature()
1867 * @param string $string String to sign
1868 * @return string
1869 */
1870 private static function __getHash($string)
1871 {
1872 return base64_encode(extension_loaded('hash') ?
1873 hash_hmac('sha1', $string, self::$__secretKey, true) : pack('H*', sha1(
1874 (str_pad(self::$__secretKey, 64, chr(0x00)) ^ (str_repeat(chr(0x5c), 64))) .
1875 pack('H*', sha1((str_pad(self::$__secretKey, 64, chr(0x00)) ^
1876 (str_repeat(chr(0x36), 64))) . $string)))));
1877 }
1878
1879 }
1880
1881 final class UpdraftPlus_S3Request
1882 {
1883 private $endpoint, $verb, $bucket, $uri, $resource = '', $parameters = array(),
1884 $amzHeaders = array(), $headers = array(
1885 'Host' => '', 'Date' => '', 'Content-MD5' => '', 'Content-Type' => ''
1886 );
1887 public $fp = false, $size = 0, $data = false, $response;
1888
1889
1890 /**
1891 * Constructor
1892 *
1893 * @param string $verb Verb
1894 * @param string $bucket Bucket name
1895 * @param string $uri Object URI
1896 * @return mixed
1897 */
1898 function __construct($verb, $bucket = '', $uri = '', $endpoint = 's3.amazonaws.com', $use_dns_bucket_name = false)
1899 {
1900 $this->endpoint = $endpoint;
1901 $this->verb = $verb;
1902 $this->bucket = $bucket;
1903 $this->uri = $uri !== '' ? '/'.str_replace('%2F', '/', rawurlencode($uri)) : '/';
1904
1905 //if ($this->bucket !== '')
1906 // $this->resource = '/'.$this->bucket.$this->uri;
1907 //else
1908 // $this->resource = $this->uri;
1909
1910 if ($this->bucket !== '')
1911 {
1912 if ($this->__dnsBucketName($this->bucket) || $use_dns_bucket_name)
1913 {
1914 $this->headers['Host'] = $this->bucket.'.'.$this->endpoint;
1915 $this->resource = '/'.$this->bucket.$this->uri;
1916 }
1917 else
1918 {
1919 $this->headers['Host'] = $this->endpoint;
1920 $this->uri = $this->uri;
1921 if ($this->bucket !== '') $this->uri = '/'.$this->bucket.$this->uri;
1922 $this->bucket = '';
1923 $this->resource = $this->uri;
1924 }
1925 }
1926 else
1927 {
1928 $this->headers['Host'] = $this->endpoint;
1929 $this->resource = $this->uri;
1930 }
1931
1932
1933 $this->headers['Date'] = gmdate('D, d M Y H:i:s T');
1934 $this->response = new STDClass;
1935 $this->response->error = false;
1936 $this->response->body = null;
1937 }
1938
1939
1940 /**
1941 * Set request parameter
1942 *
1943 * @param string $key Key
1944 * @param string $value Value
1945 * @return void
1946 */
1947 public function setParameter($key, $value)
1948 {
1949 $this->parameters[$key] = $value;
1950 }
1951
1952
1953 /**
1954 * Set request header
1955 *
1956 * @param string $key Key
1957 * @param string $value Value
1958 * @return void
1959 */
1960 public function setHeader($key, $value)
1961 {
1962 $this->headers[$key] = $value;
1963 }
1964
1965
1966 /**
1967 * Set x-amz-meta-* header
1968 *
1969 * @param string $key Key
1970 * @param string $value Value
1971 * @return void
1972 */
1973 public function setAmzHeader($key, $value)
1974 {
1975 $this->amzHeaders[$key] = $value;
1976 }
1977
1978
1979 /**
1980 * Get the S3 response
1981 *
1982 * @return object | false
1983 */
1984 public function getResponse()
1985 {
1986 $query = '';
1987 if (sizeof($this->parameters) > 0)
1988 {
1989 $query = substr($this->uri, -1) !== '?' ? '?' : '&';
1990 foreach ($this->parameters as $var => $value)
1991 if ($value == null || $value == '') $query .= $var.'&';
1992 else $query .= $var.'='.rawurlencode($value).'&';
1993 $query = substr($query, 0, -1);
1994 $this->uri .= $query;
1995
1996 if (array_key_exists('acl', $this->parameters) ||
1997 array_key_exists('location', $this->parameters) ||
1998 array_key_exists('torrent', $this->parameters) ||
1999 array_key_exists('logging', $this->parameters) ||
2000 array_key_exists('partNumber', $this->parameters) ||
2001 array_key_exists('uploads', $this->parameters) ||
2002 array_key_exists('uploadId', $this->parameters))
2003 $this->resource .= $query;
2004 }
2005 $url = (UpdraftPlus_S3::$useSSL ? 'https://' : 'http://') . ($this->headers['Host'] !== '' ? $this->headers['Host'] : $this->endpoint) . $this->uri;
2006
2007 //var_dump('bucket: ' . $this->bucket, 'uri: ' . $this->uri, 'resource: ' . $this->resource, 'url: ' . $url);
2008
2009 // Basic setup
2010 $curl = curl_init();
2011 curl_setopt($curl, CURLOPT_USERAGENT, 'S3/php');
2012
2013 if (UpdraftPlus_S3::$useSSL)
2014 {
2015 // SSL Validation can now be optional for those with broken OpenSSL installations
2016 curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, UpdraftPlus_S3::$useSSLValidation ? 2 : 0);
2017 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, UpdraftPlus_S3::$useSSLValidation ? 1 : 0);
2018
2019 if (UpdraftPlus_S3::$sslKey !== null) curl_setopt($curl, CURLOPT_SSLKEY, UpdraftPlus_S3::$sslKey);
2020 if (UpdraftPlus_S3::$sslCert !== null) curl_setopt($curl, CURLOPT_SSLCERT, UpdraftPlus_S3::$sslCert);
2021 if (UpdraftPlus_S3::$sslCACert !== null) curl_setopt($curl, CURLOPT_CAINFO, UpdraftPlus_S3::$sslCACert);
2022 }
2023
2024 curl_setopt($curl, CURLOPT_URL, $url);
2025
2026 $wp_proxy = new WP_HTTP_Proxy();
2027
2028 if (UpdraftPlus_S3::$proxy != null && isset(UpdraftPlus_S3::$proxy['host']) && $wp_proxy->send_through_proxy($url))
2029 {
2030 curl_setopt($curl, CURLOPT_PROXY, UpdraftPlus_S3::$proxy['host']);
2031 curl_setopt($curl, CURLOPT_PROXYTYPE, UpdraftPlus_S3::$proxy['type']);
2032 if (!empty(UpdraftPlus_S3::$proxy['port'])) curl_setopt($curl,CURLOPT_PROXYPORT, UpdraftPlus_S3::$proxy['port']);
2033 if (isset(UpdraftPlus_S3::$proxy['user'], UpdraftPlus_S3::$proxy['pass']) && UpdraftPlus_S3::$proxy['user'] != null && UpdraftPlus_S3::$proxy['pass'] != null) {
2034 curl_setopt($curl, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
2035 curl_setopt($curl, CURLOPT_PROXYUSERPWD, sprintf('%s:%s', UpdraftPlus_S3::$proxy['user'], UpdraftPlus_S3::$proxy['pass']));
2036 }
2037 }
2038
2039 // Headers
2040 $headers = array(); $amz = array();
2041 foreach ($this->amzHeaders as $header => $value)
2042 if (strlen($value) > 0) $headers[] = $header.': '.$value;
2043 foreach ($this->headers as $header => $value)
2044 if (strlen($value) > 0) $headers[] = $header.': '.$value;
2045
2046 // Collect AMZ headers for signature
2047 foreach ($this->amzHeaders as $header => $value)
2048 if (strlen($value) > 0) $amz[] = strtolower($header).':'.$value;
2049
2050 // AMZ headers must be sorted
2051 if (sizeof($amz) > 0)
2052 {
2053 //sort($amz);
2054 usort($amz, array(&$this, '__sortMetaHeadersCmp'));
2055 $amz = "\n".implode("\n", $amz);
2056 } else $amz = '';
2057
2058 if (UpdraftPlus_S3::hasAuth())
2059 {
2060 // Authorization string (CloudFront stringToSign should only contain a date)
2061 if ($this->headers['Host'] == 'cloudfront.amazonaws.com')
2062 $headers[] = 'Authorization: ' . UpdraftPlus_S3::__getSignature($this->headers['Date']);
2063 else
2064 {
2065 $headers[] = 'Authorization: ' . UpdraftPlus_S3::__getSignature(
2066 $this->verb."\n".
2067 $this->headers['Content-MD5']."\n".
2068 $this->headers['Content-Type']."\n".
2069 $this->headers['Date'].$amz."\n".
2070 $this->resource
2071 );
2072 }
2073 }
2074
2075 curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
2076 curl_setopt($curl, CURLOPT_HEADER, false);
2077 curl_setopt($curl, CURLOPT_RETURNTRANSFER, false);
2078 curl_setopt($curl, CURLOPT_WRITEFUNCTION, array(&$this, '__responseWriteCallback'));
2079 curl_setopt($curl, CURLOPT_HEADERFUNCTION, array(&$this, '__responseHeaderCallback'));
2080 @curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
2081
2082 // Request types
2083 switch ($this->verb)
2084 {
2085 case 'GET': break;
2086 case 'PUT': case 'POST':
2087 if ($this->fp !== false)
2088 {
2089 curl_setopt($curl, CURLOPT_PUT, true);
2090 curl_setopt($curl, CURLOPT_INFILE, $this->fp);
2091 if ($this->size >= 0)
2092 curl_setopt($curl, CURLOPT_INFILESIZE, $this->size);
2093 }
2094 elseif ($this->data !== false)
2095 {
2096 curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb);
2097 curl_setopt($curl, CURLOPT_POSTFIELDS, $this->data);
2098 }
2099 else
2100 curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb);
2101 break;
2102 case 'HEAD':
2103 curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'HEAD');
2104 curl_setopt($curl, CURLOPT_NOBODY, true);
2105 break;
2106 case 'DELETE':
2107 curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE');
2108 break;
2109 default: break;
2110 }
2111
2112 // Execute, grab errors
2113 if (curl_exec($curl))
2114 $this->response->code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
2115 else
2116 $this->response->error = array(
2117 'code' => curl_errno($curl),
2118 'message' => curl_error($curl),
2119 'resource' => $this->resource
2120 );
2121
2122 @curl_close($curl);
2123
2124 // Parse body into XML
2125 if ($this->response->error === false && isset($this->response->headers['type']) &&
2126 $this->response->headers['type'] == 'application/xml' && isset($this->response->body))
2127 {
2128 $this->response->body = simplexml_load_string($this->response->body);
2129
2130 // Grab S3 errors
2131 if (!in_array($this->response->code, array(200, 204, 206)) &&
2132 isset($this->response->body->Code, $this->response->body->Message))
2133 {
2134 $this->response->error = array(
2135 'code' => (string)$this->response->body->Code,
2136 'message' => (string)$this->response->body->Message
2137 );
2138 if (isset($this->response->body->Resource))
2139 $this->response->error['resource'] = (string)$this->response->body->Resource;
2140 unset($this->response->body);
2141 }
2142 }
2143
2144 // Clean up file resources
2145 if ($this->fp !== false && is_resource($this->fp)) fclose($this->fp);
2146
2147 return $this->response;
2148 }
2149
2150 /**
2151 * Sort compare for meta headers
2152 *
2153 * @internal Used to sort x-amz meta headers
2154 * @param string $a String A
2155 * @param string $b String B
2156 * @return integer
2157 */
2158 private function __sortMetaHeadersCmp($a, $b)
2159 {
2160 $lenA = strpos($a, ':');
2161 $lenB = strpos($b, ':');
2162 $minLen = min($lenA, $lenB);
2163 $ncmp = strncmp($a, $b, $minLen);
2164 if ($lenA == $lenB) return $ncmp;
2165 if (0 == $ncmp) return $lenA < $lenB ? -1 : 1;
2166 return $ncmp;
2167 }
2168
2169 /**
2170 * CURL write callback
2171 *
2172 * @param resource $curl CURL resource
2173 * @param string $data Data
2174 * @return integer
2175 */
2176 private function __responseWriteCallback($curl, $data)
2177 {
2178 if (in_array($this->response->code, array(200, 206)) && $this->fp !== false)
2179 return fwrite($this->fp, $data);
2180 else
2181 $this->response->body = (empty($this->response->body)) ? $data : $this->response->body.$data;
2182 return strlen($data);
2183 }
2184
2185
2186 /**
2187 * Check DNS conformity
2188 *
2189 * @param string $bucket Bucket name
2190 * @return boolean
2191 */
2192 private function __dnsBucketName($bucket)
2193 {
2194 # A DNS bucket name cannot have len>63
2195 # A DNS bucket name must have a character in other than a-z, 0-9, . -
2196 # The purpose of this second check is not clear - is it that there's some limitation somewhere on bucket names that match that pattern that means that the bucket must be accessed by hostname?
2197 if (strlen($bucket) > 63 || !preg_match("/[^a-z0-9\.-]/", $bucket)) return false;
2198 # A DNS bucket name cannot contain -.
2199 if (strstr($bucket, '-.') !== false) return false;
2200 # A DNS bucket name cannot contain ..
2201 if (strstr($bucket, '..') !== false) return false;
2202 # A DNS bucket name must begin with 0-9a-z
2203 if (!preg_match("/^[0-9a-z]/", $bucket)) return false;
2204 # A DNS bucket name must end with 0-9 a-z
2205 if (!preg_match("/[0-9a-z]$/", $bucket)) return false;
2206 return true;
2207 }
2208
2209 /**
2210 * CURL header callback
2211 *
2212 * @param resource $curl CURL resource
2213 * @param string $data Data
2214 * @return integer
2215 */
2216 private function __responseHeaderCallback($curl, $data)
2217 {
2218 if (($strlen = strlen($data)) <= 2) return $strlen;
2219 if (substr($data, 0, 4) == 'HTTP')
2220 $this->response->code = (int)substr($data, 9, 3);
2221 else
2222 {
2223 $data = trim($data);
2224 if (strpos($data, ': ') === false) return $strlen;
2225 list($header, $value) = explode(': ', $data, 2);
2226 if ($header == 'Last-Modified')
2227 $this->response->headers['time'] = strtotime($value);
2228 elseif ($header == 'Content-Length')
2229 $this->response->headers['size'] = (int)$value;
2230 elseif ($header == 'Content-Type')
2231 $this->response->headers['type'] = $value;
2232 elseif ($header == 'ETag')
2233 $this->response->headers['hash'] = $value{0} == '"' ? substr($value, 1, -1) : $value;
2234 elseif (preg_match('/^x-amz-meta-.*$/', $header))
2235 $this->response->headers[$header] = $value;
2236 }
2237 return $strlen;
2238 }
2239
2240 }
2241
2242 class UpdraftPlus_S3Exception extends Exception {
2243 function __construct($message, $file, $line, $code = 0)
2244 {
2245 parent::__construct($message, $code);
2246 $this->file = $file;
2247 $this->line = $line;
2248 }
2249 }
2250