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