UpdraftPlus WordPress Backup Plugin - Version 0.9.11

Version Description

  • 12/06/2012 =
  • Updated to latest S3.php library (to allow for future enhancements)
Download this release

Release Info

Developer DavidAnderson
Plugin Icon 128x128 UpdraftPlus WordPress Backup Plugin
Version 0.9.11
Comparing to
See all releases

Code changes from version 0.9.10 to 0.9.11

Files changed (3) hide show
  1. includes/S3.php +890 -221
  2. readme.txt +4 -1
  3. updraftplus.php +2 -2
includes/S3.php CHANGED
@@ -1,8 +1,8 @@
1
  <?php
2
  /**
3
- * $Id: S3.php 47 2009-07-20 01:25:40Z don.schonknecht $
4
  *
5
- * Copyright (c) 2008, Donovan Schönknecht. All rights reserved.
6
  *
7
  * Redistribution and use in source and binary forms, with or without
8
  * modification, are permitted provided that the following conditions are met:
@@ -32,19 +32,37 @@
32
  * Amazon S3 PHP class
33
  *
34
  * @link http://undesigned.org.za/2007/10/22/amazon-s3-php-class
35
- * @version 0.4.0
36
  */
37
- class S3 {
 
38
  // ACL flags
39
  const ACL_PRIVATE = 'private';
40
  const ACL_PUBLIC_READ = 'public-read';
41
  const ACL_PUBLIC_READ_WRITE = 'public-read-write';
42
  const ACL_AUTHENTICATED_READ = 'authenticated-read';
43
 
44
- public static $useSSL = true;
 
45
 
46
- private static $__accessKey; // AWS Access key
47
- private static $__secretKey; // AWS Secret key
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
 
49
 
50
  /**
@@ -55,13 +73,26 @@ class S3 {
55
  * @param boolean $useSSL Enable SSL
56
  * @return void
57
  */
58
- public function __construct($accessKey = null, $secretKey = null, $useSSL = true) {
 
59
  if ($accessKey !== null && $secretKey !== null)
60
  self::setAuth($accessKey, $secretKey);
61
  self::$useSSL = $useSSL;
 
62
  }
63
 
64
 
 
 
 
 
 
 
 
 
 
 
 
65
  /**
66
  * Set AWS access key and secret key
67
  *
@@ -69,31 +100,152 @@ class S3 {
69
  * @param string $secretKey Secret key
70
  * @return void
71
  */
72
- public static function setAuth($accessKey, $secretKey) {
 
73
  self::$__accessKey = $accessKey;
74
  self::$__secretKey = $secretKey;
75
  }
76
 
77
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  /**
79
  * Get a list of buckets
80
  *
81
  * @param boolean $detailed Returns detailed bucket list when true
82
  * @return array | false
83
  */
84
- public static function listBuckets($detailed = false) {
85
- $rest = new S3Request('GET', '', '');
 
86
  $rest = $rest->getResponse();
87
  if ($rest->error === false && $rest->code !== 200)
88
  $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
89
- if ($rest->error !== false) {
90
- trigger_error(sprintf("S3::listBuckets(): [%s] %s", $rest->error['code'], $rest->error['message']), E_USER_WARNING);
 
 
91
  return false;
92
  }
93
  $results = array();
94
  if (!isset($rest->body->Buckets)) return $results;
95
 
96
- if ($detailed) {
 
97
  if (isset($rest->body->Owner, $rest->body->Owner->ID, $rest->body->Owner->DisplayName))
98
  $results['owner'] = array(
99
  'id' => (string)$rest->body->Owner->ID, 'name' => (string)$rest->body->Owner->ID
@@ -123,8 +275,10 @@ class S3 {
123
  * @param boolean $returnCommonPrefixes Set to true to return CommonPrefixes
124
  * @return array | false
125
  */
126
- public static function getBucket($bucket, $prefix = null, $marker = null, $maxKeys = null, $delimiter = null, $returnCommonPrefixes = false) {
127
- $rest = new S3Request('GET', $bucket, '');
 
 
128
  if ($prefix !== null && $prefix !== '') $rest->setParameter('prefix', $prefix);
129
  if ($marker !== null && $marker !== '') $rest->setParameter('marker', $marker);
130
  if ($maxKeys !== null && $maxKeys !== '') $rest->setParameter('max-keys', $maxKeys);
@@ -132,8 +286,10 @@ class S3 {
132
  $response = $rest->getResponse();
133
  if ($response->error === false && $response->code !== 200)
134
  $response->error = array('code' => $response->code, 'message' => 'Unexpected HTTP status');
135
- if ($response->error !== false) {
136
- trigger_error(sprintf("S3::getBucket(): [%s] %s", $response->error['code'], $response->error['message']), E_USER_WARNING);
 
 
137
  return false;
138
  }
139
 
@@ -141,7 +297,8 @@ class S3 {
141
 
142
  $nextMarker = null;
143
  if (isset($response->body, $response->body->Contents))
144
- foreach ($response->body->Contents as $c) {
 
145
  $results[(string)$c->Key] = array(
146
  'name' => (string)$c->Key,
147
  'time' => strtotime((string)$c->LastModified),
@@ -163,16 +320,18 @@ class S3 {
163
 
164
  // Loop through truncated results if maxKeys isn't specified
165
  if ($maxKeys == null && $nextMarker !== null && (string)$response->body->IsTruncated == 'true')
166
- do {
167
- $rest = new S3Request('GET', $bucket, '');
 
168
  if ($prefix !== null && $prefix !== '') $rest->setParameter('prefix', $prefix);
169
  $rest->setParameter('marker', $nextMarker);
170
  if ($delimiter !== null && $delimiter !== '') $rest->setParameter('delimiter', $delimiter);
171
 
172
- if (($response = $rest->getResponse(true)) == false || $response->code !== 200) break;
173
 
174
  if (isset($response->body, $response->body->Contents))
175
- foreach ($response->body->Contents as $c) {
 
176
  $results[(string)$c->Key] = array(
177
  'name' => (string)$c->Key,
178
  'time' => strtotime((string)$c->LastModified),
@@ -203,14 +362,16 @@ class S3 {
203
  * @param string $location Set as "EU" to create buckets hosted in Europe
204
  * @return boolean
205
  */
206
- public static function putBucket($bucket, $acl = self::ACL_PRIVATE, $location = false) {
207
- $rest = new S3Request('PUT', $bucket, '');
 
208
  $rest->setAmzHeader('x-amz-acl', $acl);
209
 
210
- if ($location !== false) {
 
211
  $dom = new DOMDocument;
212
  $createBucketConfiguration = $dom->createElement('CreateBucketConfiguration');
213
- $locationConstraint = $dom->createElement('LocationConstraint', strtoupper($location));
214
  $createBucketConfiguration->appendChild($locationConstraint);
215
  $dom->appendChild($createBucketConfiguration);
216
  $rest->data = $dom->saveXML();
@@ -221,9 +382,10 @@ class S3 {
221
 
222
  if ($rest->error === false && $rest->code !== 200)
223
  $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
224
- if ($rest->error !== false) {
225
- trigger_error(sprintf("S3::putBucket({$bucket}, {$acl}, {$location}): [%s] %s",
226
- $rest->error['code'], $rest->error['message']), E_USER_WARNING);
 
227
  return false;
228
  }
229
  return true;
@@ -236,14 +398,16 @@ class S3 {
236
  * @param string $bucket Bucket name
237
  * @return boolean
238
  */
239
- public static function deleteBucket($bucket) {
240
- $rest = new S3Request('DELETE', $bucket);
 
241
  $rest = $rest->getResponse();
242
  if ($rest->error === false && $rest->code !== 204)
243
  $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
244
- if ($rest->error !== false) {
245
- trigger_error(sprintf("S3::deleteBucket({$bucket}): [%s] %s",
246
- $rest->error['code'], $rest->error['message']), E_USER_WARNING);
 
247
  return false;
248
  }
249
  return true;
@@ -257,14 +421,15 @@ class S3 {
257
  * @param mixed $md5sum Use MD5 hash (supply a string if you want to use your own)
258
  * @return array | false
259
  */
260
- public static function inputFile($file, $md5sum = true) {
261
- if (!file_exists($file) || !is_file($file) || !is_readable($file)) {
262
- trigger_error('S3::inputFile(): Unable to open input file: '.$file, E_USER_WARNING);
 
 
263
  return false;
264
  }
265
- return array('file' => $file, 'size' => filesize($file),
266
- 'md5sum' => $md5sum !== false ? (is_string($md5sum) ? $md5sum :
267
- base64_encode(md5_file($file, true))) : '');
268
  }
269
 
270
 
@@ -276,9 +441,11 @@ class S3 {
276
  * @param string $md5sum MD5 hash to send (optional)
277
  * @return array | false
278
  */
279
- public static function inputResource(&$resource, $bufferSize, $md5sum = '') {
280
- if (!is_resource($resource) || $bufferSize < 0) {
281
- trigger_error('S3::inputResource(): Invalid resource or buffer size', E_USER_WARNING);
 
 
282
  return false;
283
  }
284
  $input = array('size' => $bufferSize, 'md5sum' => $md5sum);
@@ -296,13 +463,15 @@ class S3 {
296
  * @param constant $acl ACL constant
297
  * @param array $metaHeaders Array of x-amz-meta-* headers
298
  * @param array $requestHeaders Array of request headers or content type as a string
 
299
  * @return boolean
300
  */
301
- public static function putObject($input, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array()) {
 
302
  if ($input === false) return false;
303
- $rest = new S3Request('PUT', $bucket, $uri);
304
 
305
- if (is_string($input)) $input = array(
306
  'data' => $input, 'size' => strlen($input),
307
  'md5sum' => base64_encode(md5($input, true))
308
  );
@@ -332,7 +501,8 @@ class S3 {
332
  $input['type'] = $requestHeaders;
333
 
334
  // Content-Type
335
- if (!isset($input['type'])) {
 
336
  if (isset($requestHeaders['Content-Type']))
337
  $input['type'] =& $requestHeaders['Content-Type'];
338
  elseif (isset($input['file']))
@@ -341,8 +511,12 @@ class S3 {
341
  $input['type'] = 'application/octet-stream';
342
  }
343
 
 
 
 
344
  // We need to post with Content-Length and Content-Type, MD5 is optional
345
- if ($rest->size >= 0 && ($rest->fp !== false || $rest->data !== false)) {
 
346
  $rest->setHeader('Content-Type', $input['type']);
347
  if (isset($input['md5sum'])) $rest->setHeader('Content-MD5', $input['md5sum']);
348
 
@@ -354,8 +528,10 @@ class S3 {
354
 
355
  if ($rest->response->error === false && $rest->response->code !== 200)
356
  $rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status');
357
- if ($rest->response->error !== false) {
358
- trigger_error(sprintf("S3::putObject(): [%s] %s", $rest->response->error['code'], $rest->response->error['message']), E_USER_WARNING);
 
 
359
  return false;
360
  }
361
  return true;
@@ -373,7 +549,8 @@ class S3 {
373
  * @param string $contentType Content type
374
  * @return boolean
375
  */
376
- public static function putObjectFile($file, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = null) {
 
377
  return self::putObject(self::inputFile($file), $bucket, $uri, $acl, $metaHeaders, $contentType);
378
  }
379
 
@@ -389,7 +566,8 @@ class S3 {
389
  * @param string $contentType Content type
390
  * @return boolean
391
  */
392
- public static function putObjectString($string, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = 'text/plain') {
 
393
  return self::putObject($string, $bucket, $uri, $acl, $metaHeaders, $contentType);
394
  }
395
 
@@ -402,9 +580,11 @@ class S3 {
402
  * @param mixed $saveTo Filename or resource to write to
403
  * @return mixed
404
  */
405
- public static function getObject($bucket, $uri, $saveTo = false) {
406
- $rest = new S3Request('GET', $bucket, $uri);
407
- if ($saveTo !== false) {
 
 
408
  if (is_resource($saveTo))
409
  $rest->fp =& $saveTo;
410
  else
@@ -417,9 +597,10 @@ class S3 {
417
 
418
  if ($rest->response->error === false && $rest->response->code !== 200)
419
  $rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status');
420
- if ($rest->response->error !== false) {
421
- trigger_error(sprintf("S3::getObject({$bucket}, {$uri}): [%s] %s",
422
- $rest->response->error['code'], $rest->response->error['message']), E_USER_WARNING);
 
423
  return false;
424
  }
425
  return $rest->response;
@@ -434,14 +615,16 @@ class S3 {
434
  * @param boolean $returnInfo Return response information
435
  * @return mixed | false
436
  */
437
- public static function getObjectInfo($bucket, $uri, $returnInfo = true) {
438
- $rest = new S3Request('HEAD', $bucket, $uri);
 
439
  $rest = $rest->getResponse();
440
  if ($rest->error === false && ($rest->code !== 200 && $rest->code !== 404))
441
  $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
442
- if ($rest->error !== false) {
443
- trigger_error(sprintf("S3::getObjectInfo({$bucket}, {$uri}): [%s] %s",
444
- $rest->error['code'], $rest->error['message']), E_USER_WARNING);
 
445
  return false;
446
  }
447
  return $rest->code == 200 ? $returnInfo ? $rest->headers : true : false;
@@ -458,23 +641,29 @@ class S3 {
458
  * @param constant $acl ACL constant
459
  * @param array $metaHeaders Optional array of x-amz-meta-* headers
460
  * @param array $requestHeaders Optional array of request headers (content type, disposition, etc.)
 
461
  * @return mixed | false
462
  */
463
- public static function copyObject($srcBucket, $srcUri, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array()) {
464
- $rest = new S3Request('PUT', $bucket, $uri);
 
465
  $rest->setHeader('Content-Length', 0);
466
  foreach ($requestHeaders as $h => $v) $rest->setHeader($h, $v);
467
  foreach ($metaHeaders as $h => $v) $rest->setAmzHeader('x-amz-meta-'.$h, $v);
 
 
468
  $rest->setAmzHeader('x-amz-acl', $acl);
469
- $rest->setAmzHeader('x-amz-copy-source', sprintf('/%s/%s', $srcBucket, $srcUri));
470
  if (sizeof($requestHeaders) > 0 || sizeof($metaHeaders) > 0)
471
  $rest->setAmzHeader('x-amz-metadata-directive', 'REPLACE');
 
472
  $rest = $rest->getResponse();
473
  if ($rest->error === false && $rest->code !== 200)
474
  $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
475
- if ($rest->error !== false) {
476
- trigger_error(sprintf("S3::copyObject({$srcBucket}, {$srcUri}, {$bucket}, {$uri}): [%s] %s",
477
- $rest->error['code'], $rest->error['message']), E_USER_WARNING);
 
478
  return false;
479
  }
480
  return isset($rest->body->LastModified, $rest->body->ETag) ? array(
@@ -492,14 +681,17 @@ class S3 {
492
  * @param string $targetPrefix Log prefix (e,g; domain.com-)
493
  * @return boolean
494
  */
495
- public static function setBucketLogging($bucket, $targetBucket, $targetPrefix = null) {
 
496
  // The S3 log delivery group has to be added to the target bucket's ACP
497
- if ($targetBucket !== null && ($acp = self::getAccessControlPolicy($targetBucket, '')) !== false) {
 
498
  // Only add permissions to the target bucket when they do not exist
499
  $aclWriteSet = false;
500
  $aclReadSet = false;
501
  foreach ($acp['acl'] as $acl)
502
- if ($acl['type'] == 'Group' && $acl['uri'] == 'http://acs.amazonaws.com/groups/s3/LogDelivery') {
 
503
  if ($acl['permission'] == 'WRITE') $aclWriteSet = true;
504
  elseif ($acl['permission'] == 'READ_ACP') $aclReadSet = true;
505
  }
@@ -515,7 +707,8 @@ class S3 {
515
  $dom = new DOMDocument;
516
  $bucketLoggingStatus = $dom->createElement('BucketLoggingStatus');
517
  $bucketLoggingStatus->setAttribute('xmlns', 'http://s3.amazonaws.com/doc/2006-03-01/');
518
- if ($targetBucket !== null) {
 
519
  if ($targetPrefix == null) $targetPrefix = $bucket . '-';
520
  $loggingEnabled = $dom->createElement('LoggingEnabled');
521
  $loggingEnabled->appendChild($dom->createElement('TargetBucket', $targetBucket));
@@ -525,7 +718,7 @@ class S3 {
525
  }
526
  $dom->appendChild($bucketLoggingStatus);
527
 
528
- $rest = new S3Request('PUT', $bucket, '');
529
  $rest->setParameter('logging', null);
530
  $rest->data = $dom->saveXML();
531
  $rest->size = strlen($rest->data);
@@ -533,9 +726,10 @@ class S3 {
533
  $rest = $rest->getResponse();
534
  if ($rest->error === false && $rest->code !== 200)
535
  $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
536
- if ($rest->error !== false) {
537
- trigger_error(sprintf("S3::setBucketLogging({$bucket}, {$uri}): [%s] %s",
538
- $rest->error['code'], $rest->error['message']), E_USER_WARNING);
 
539
  return false;
540
  }
541
  return true;
@@ -551,15 +745,17 @@ class S3 {
551
  * @param string $bucket Bucket name
552
  * @return array | false
553
  */
554
- public static function getBucketLogging($bucket) {
555
- $rest = new S3Request('GET', $bucket, '');
 
556
  $rest->setParameter('logging', null);
557
  $rest = $rest->getResponse();
558
  if ($rest->error === false && $rest->code !== 200)
559
  $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
560
- if ($rest->error !== false) {
561
- trigger_error(sprintf("S3::getBucketLogging({$bucket}): [%s] %s",
562
- $rest->error['code'], $rest->error['message']), E_USER_WARNING);
 
563
  return false;
564
  }
565
  if (!isset($rest->body->LoggingEnabled)) return false; // No logging
@@ -576,7 +772,8 @@ class S3 {
576
  * @param string $bucket Bucket name
577
  * @return boolean
578
  */
579
- public static function disableBucketLogging($bucket) {
 
580
  return self::setBucketLogging($bucket, null);
581
  }
582
 
@@ -587,15 +784,17 @@ class S3 {
587
  * @param string $bucket Bucket name
588
  * @return string | false
589
  */
590
- public static function getBucketLocation($bucket) {
591
- $rest = new S3Request('GET', $bucket, '');
 
592
  $rest->setParameter('location', null);
593
  $rest = $rest->getResponse();
594
  if ($rest->error === false && $rest->code !== 200)
595
  $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
596
- if ($rest->error !== false) {
597
- trigger_error(sprintf("S3::getBucketLocation({$bucket}): [%s] %s",
598
- $rest->error['code'], $rest->error['message']), E_USER_WARNING);
 
599
  return false;
600
  }
601
  return (isset($rest->body[0]) && (string)$rest->body[0] !== '') ? (string)$rest->body[0] : 'US';
@@ -610,7 +809,8 @@ class S3 {
610
  * @param array $acp Access Control Policy Data (same as the data returned from getAccessControlPolicy)
611
  * @return boolean
612
  */
613
- public static function setAccessControlPolicy($bucket, $uri = '', $acp = array()) {
 
614
  $dom = new DOMDocument;
615
  $dom->formatOutput = true;
616
  $accessControlPolicy = $dom->createElement('AccessControlPolicy');
@@ -622,17 +822,23 @@ class S3 {
622
  $owner->appendChild($dom->createElement('DisplayName', $acp['owner']['name']));
623
  $accessControlPolicy->appendChild($owner);
624
 
625
- foreach ($acp['acl'] as $g) {
 
626
  $grant = $dom->createElement('Grant');
627
  $grantee = $dom->createElement('Grantee');
628
  $grantee->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
629
- if (isset($g['id'])) { // CanonicalUser (DisplayName is omitted)
 
630
  $grantee->setAttribute('xsi:type', 'CanonicalUser');
631
  $grantee->appendChild($dom->createElement('ID', $g['id']));
632
- } elseif (isset($g['email'])) { // AmazonCustomerByEmail
 
 
633
  $grantee->setAttribute('xsi:type', 'AmazonCustomerByEmail');
634
  $grantee->appendChild($dom->createElement('EmailAddress', $g['email']));
635
- } elseif ($g['type'] == 'Group') { // Group
 
 
636
  $grantee->setAttribute('xsi:type', 'Group');
637
  $grantee->appendChild($dom->createElement('URI', $g['uri']));
638
  }
@@ -644,7 +850,7 @@ class S3 {
644
  $accessControlPolicy->appendChild($accessControlList);
645
  $dom->appendChild($accessControlPolicy);
646
 
647
- $rest = new S3Request('PUT', $bucket, $uri);
648
  $rest->setParameter('acl', null);
649
  $rest->data = $dom->saveXML();
650
  $rest->size = strlen($rest->data);
@@ -652,9 +858,10 @@ class S3 {
652
  $rest = $rest->getResponse();
653
  if ($rest->error === false && $rest->code !== 200)
654
  $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
655
- if ($rest->error !== false) {
656
- trigger_error(sprintf("S3::setAccessControlPolicy({$bucket}, {$uri}): [%s] %s",
657
- $rest->error['code'], $rest->error['message']), E_USER_WARNING);
 
658
  return false;
659
  }
660
  return true;
@@ -668,28 +875,33 @@ class S3 {
668
  * @param string $uri Object URI
669
  * @return mixed | false
670
  */
671
- public static function getAccessControlPolicy($bucket, $uri = '') {
672
- $rest = new S3Request('GET', $bucket, $uri);
 
673
  $rest->setParameter('acl', null);
674
  $rest = $rest->getResponse();
675
  if ($rest->error === false && $rest->code !== 200)
676
  $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
677
- if ($rest->error !== false) {
678
- trigger_error(sprintf("S3::getAccessControlPolicy({$bucket}, {$uri}): [%s] %s",
679
- $rest->error['code'], $rest->error['message']), E_USER_WARNING);
 
680
  return false;
681
  }
682
 
683
  $acp = array();
684
- if (isset($rest->body->Owner, $rest->body->Owner->ID, $rest->body->Owner->DisplayName)) {
685
  $acp['owner'] = array(
686
  'id' => (string)$rest->body->Owner->ID, 'name' => (string)$rest->body->Owner->DisplayName
687
  );
688
- }
689
- if (isset($rest->body->AccessControlList)) {
 
690
  $acp['acl'] = array();
691
- foreach ($rest->body->AccessControlList->Grant as $grant) {
692
- foreach ($grant->Grantee as $grantee) {
 
 
693
  if (isset($grantee->ID, $grantee->DisplayName)) // CanonicalUser
694
  $acp['acl'][] = array(
695
  'type' => 'CanonicalUser',
@@ -724,14 +936,16 @@ class S3 {
724
  * @param string $uri Object URI
725
  * @return boolean
726
  */
727
- public static function deleteObject($bucket, $uri) {
728
- $rest = new S3Request('DELETE', $bucket, $uri);
 
729
  $rest = $rest->getResponse();
730
  if ($rest->error === false && $rest->code !== 204)
731
  $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
732
- if ($rest->error !== false) {
733
- trigger_error(sprintf("S3::deleteObject(): [%s] %s",
734
- $rest->error['code'], $rest->error['message']), E_USER_WARNING);
 
735
  return false;
736
  }
737
  return true;
@@ -748,14 +962,58 @@ class S3 {
748
  * @param boolean $https Use HTTPS ($hostBucket should be false for SSL verification)
749
  * @return string
750
  */
751
- public static function getAuthenticatedURL($bucket, $uri, $lifetime, $hostBucket = false, $https = false) {
 
752
  $expires = time() + $lifetime;
753
- $uri = str_replace('%2F', '/', rawurlencode($uri)); // URI should be encoded (thanks Sean O'Dea)
754
  return sprintf(($https ? 'https' : 'http').'://%s/%s?AWSAccessKeyId=%s&Expires=%u&Signature=%s',
755
- $hostBucket ? $bucket : $bucket.'.s3.amazonaws.com', $uri, self::$__accessKey, $expires,
 
756
  urlencode(self::__getHash("GET\n\n\n{$expires}\n/{$bucket}/{$uri}")));
757
  }
758
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
759
  /**
760
  * Get upload POST parameters for form uploads
761
  *
@@ -770,7 +1028,9 @@ class S3 {
770
  * @param boolean $flashVars Includes additional "Filename" variable posted by Flash
771
  * @return object
772
  */
773
- public static function getHttpUploadPostParams($bucket, $uriPrefix = '', $acl = self::ACL_PRIVATE, $lifetime = 3600, $maxFileSize = 5242880, $successRedirect = "201", $amzHeaders = array(), $headers = array(), $flashVars = false) {
 
 
774
  // Create policy object
775
  $policy = new stdClass;
776
  $policy->expiration = gmdate('Y-m-d\TH:i:s\Z', (time() + $lifetime));
@@ -785,16 +1045,22 @@ class S3 {
785
  $obj->success_action_redirect = $successRedirect;
786
  array_push($policy->conditions, $obj);
787
 
 
 
 
788
  array_push($policy->conditions, array('starts-with', '$key', $uriPrefix));
789
  if ($flashVars) array_push($policy->conditions, array('starts-with', '$Filename', ''));
790
  foreach (array_keys($headers) as $headerKey)
791
  array_push($policy->conditions, array('starts-with', '$'.$headerKey, ''));
792
- foreach ($amzHeaders as $headerKey => $headerVal) {
793
- $obj = new stdClass; $obj->{$headerKey} = (string)$headerVal; array_push($policy->conditions, $obj);
 
 
 
794
  }
795
  array_push($policy->conditions, array('content-length-range', 0, $maxFileSize));
796
  $policy = base64_encode(str_replace('\/', '/', json_encode($policy)));
797
-
798
  // Create parameters
799
  $params = new stdClass;
800
  $params->AWSAccessKeyId = self::$__accessKey;
@@ -811,6 +1077,7 @@ class S3 {
811
  return $params;
812
  }
813
 
 
814
  /**
815
  * Create a CloudFront distribution
816
  *
@@ -818,21 +1085,46 @@ class S3 {
818
  * @param boolean $enabled Enabled (true/false)
819
  * @param array $cnames Array containing CNAME aliases
820
  * @param string $comment Use the bucket name as the hostname
 
 
 
821
  * @return array | false
822
  */
823
- public static function createDistribution($bucket, $enabled = true, $cnames = array(), $comment = '') {
 
 
 
 
 
 
 
 
 
824
  self::$useSSL = true; // CloudFront requires SSL
825
- $rest = new S3Request('POST', '', '2008-06-30/distribution', 'cloudfront.amazonaws.com');
826
- $rest->data = self::__getCloudFrontDistributionConfigXML($bucket.'.s3.amazonaws.com', $enabled, $comment, (string)microtime(true), $cnames);
 
 
 
 
 
 
 
 
 
 
827
  $rest->size = strlen($rest->data);
828
  $rest->setHeader('Content-Type', 'application/xml');
829
  $rest = self::__getCloudFrontResponse($rest);
830
 
 
 
831
  if ($rest->error === false && $rest->code !== 201)
832
  $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
833
- if ($rest->error !== false) {
834
- trigger_error(sprintf("S3::createDistribution({$bucket}, ".(int)$enabled.", '$comment'): [%s] %s",
835
- $rest->error['code'], $rest->error['message']), E_USER_WARNING);
 
836
  return false;
837
  } elseif ($rest->body instanceof SimpleXMLElement)
838
  return self::__parseCloudFrontDistributionConfig($rest->body);
@@ -846,20 +1138,35 @@ class S3 {
846
  * @param string $distributionId Distribution ID from listDistributions()
847
  * @return array | false
848
  */
849
- public static function getDistribution($distributionId) {
 
 
 
 
 
 
 
 
 
850
  self::$useSSL = true; // CloudFront requires SSL
851
- $rest = new S3Request('GET', '', '2008-06-30/distribution/'.$distributionId, 'cloudfront.amazonaws.com');
852
  $rest = self::__getCloudFrontResponse($rest);
853
 
 
 
854
  if ($rest->error === false && $rest->code !== 200)
855
  $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
856
- if ($rest->error !== false) {
857
- trigger_error(sprintf("S3::getDistribution($distributionId): [%s] %s",
858
- $rest->error['code'], $rest->error['message']), E_USER_WARNING);
 
859
  return false;
860
- } elseif ($rest->body instanceof SimpleXMLElement) {
 
 
861
  $dist = self::__parseCloudFrontDistributionConfig($rest->body);
862
  $dist['hash'] = $rest->headers['hash'];
 
863
  return $dist;
864
  }
865
  return false;
@@ -872,19 +1179,42 @@ class S3 {
872
  * @param array $dist Distribution array info identical to output of getDistribution()
873
  * @return array | false
874
  */
875
- public static function updateDistribution($dist) {
 
 
 
 
 
 
 
 
 
 
876
  self::$useSSL = true; // CloudFront requires SSL
877
- $rest = new S3Request('PUT', '', '2008-06-30/distribution/'.$dist['id'].'/config', 'cloudfront.amazonaws.com');
878
- $rest->data = self::__getCloudFrontDistributionConfigXML($dist['origin'], $dist['enabled'], $dist['comment'], $dist['callerReference'], $dist['cnames']);
 
 
 
 
 
 
 
 
 
 
879
  $rest->size = strlen($rest->data);
880
  $rest->setHeader('If-Match', $dist['hash']);
881
  $rest = self::__getCloudFrontResponse($rest);
882
 
 
 
883
  if ($rest->error === false && $rest->code !== 200)
884
  $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
885
- if ($rest->error !== false) {
886
- trigger_error(sprintf("S3::updateDistribution({$dist['id']}, ".(int)$enabled.", '$comment'): [%s] %s",
887
- $rest->error['code'], $rest->error['message']), E_USER_WARNING);
 
888
  return false;
889
  } else {
890
  $dist = self::__parseCloudFrontDistributionConfig($rest->body);
@@ -901,17 +1231,30 @@ class S3 {
901
  * @param array $dist Distribution array info identical to output of getDistribution()
902
  * @return boolean
903
  */
904
- public static function deleteDistribution($dist) {
 
 
 
 
 
 
 
 
 
 
905
  self::$useSSL = true; // CloudFront requires SSL
906
  $rest = new S3Request('DELETE', '', '2008-06-30/distribution/'.$dist['id'], 'cloudfront.amazonaws.com');
907
  $rest->setHeader('If-Match', $dist['hash']);
908
  $rest = self::__getCloudFrontResponse($rest);
909
 
 
 
910
  if ($rest->error === false && $rest->code !== 204)
911
  $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
912
- if ($rest->error !== false) {
913
- trigger_error(sprintf("S3::deleteDistribution({$dist['id']}): [%s] %s",
914
- $rest->error['code'], $rest->error['message']), E_USER_WARNING);
 
915
  return false;
916
  }
917
  return true;
@@ -923,27 +1266,189 @@ class S3 {
923
  *
924
  * @return array
925
  */
926
- public static function listDistributions() {
 
 
 
 
 
 
 
 
 
927
  self::$useSSL = true; // CloudFront requires SSL
928
- $rest = new S3Request('GET', '', '2008-06-30/distribution', 'cloudfront.amazonaws.com');
929
  $rest = self::__getCloudFrontResponse($rest);
 
930
 
931
  if ($rest->error === false && $rest->code !== 200)
932
  $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
933
- if ($rest->error !== false) {
934
- trigger_error(sprintf("S3::listDistributions(): [%s] %s",
935
- $rest->error['code'], $rest->error['message']), E_USER_WARNING);
 
936
  return false;
937
- } elseif ($rest->body instanceof SimpleXMLElement && isset($rest->body->DistributionSummary)) {
 
 
938
  $list = array();
939
- if (isset($rest->body->Marker, $rest->body->MaxItems, $rest->body->IsTruncated)) {
 
940
  //$info['marker'] = (string)$rest->body->Marker;
941
  //$info['maxItems'] = (int)$rest->body->MaxItems;
942
  //$info['isTruncated'] = (string)$rest->body->IsTruncated == 'true' ? true : false;
943
  }
944
- foreach ($rest->body->DistributionSummary as $summary) {
945
  $list[(string)$summary->Id] = self::__parseCloudFrontDistributionConfig($summary);
946
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
947
  return $list;
948
  }
949
  return array();
@@ -953,26 +1458,46 @@ class S3 {
953
  /**
954
  * Get a DistributionConfig DOMDocument
955
  *
 
 
956
  * @internal Used to create XML in createDistribution() and updateDistribution()
957
- * @param string $bucket Origin bucket
958
  * @param boolean $enabled Enabled (true/false)
959
  * @param string $comment Comment to append
960
  * @param string $callerReference Caller reference
961
  * @param array $cnames Array of CNAME aliases
 
 
 
962
  * @return string
963
  */
964
- private static function __getCloudFrontDistributionConfigXML($bucket, $enabled, $comment, $callerReference = '0', $cnames = array()) {
 
965
  $dom = new DOMDocument('1.0', 'UTF-8');
966
  $dom->formatOutput = true;
967
  $distributionConfig = $dom->createElement('DistributionConfig');
968
- $distributionConfig->setAttribute('xmlns', 'http://cloudfront.amazonaws.com/doc/2008-06-30/');
969
- $distributionConfig->appendChild($dom->createElement('Origin', $bucket));
 
 
 
 
 
 
 
970
  $distributionConfig->appendChild($dom->createElement('CallerReference', $callerReference));
971
  foreach ($cnames as $cname)
972
  $distributionConfig->appendChild($dom->createElement('CNAME', $cname));
973
  if ($comment !== '') $distributionConfig->appendChild($dom->createElement('Comment', $comment));
974
  $distributionConfig->appendChild($dom->createElement('Enabled', $enabled ? 'true' : 'false'));
 
 
 
 
 
 
975
  $dom->appendChild($distributionConfig);
 
976
  return $dom->saveXML();
977
  }
978
 
@@ -980,32 +1505,61 @@ class S3 {
980
  /**
981
  * Parse a CloudFront distribution config
982
  *
 
 
983
  * @internal Used to parse the CloudFront DistributionConfig node to an array
984
  * @param object &$node DOMNode
985
  * @return array
986
  */
987
- private static function __parseCloudFrontDistributionConfig(&$node) {
 
 
 
 
988
  $dist = array();
989
- if (isset($node->Id, $node->Status, $node->LastModifiedTime, $node->DomainName)) {
 
990
  $dist['id'] = (string)$node->Id;
991
  $dist['status'] = (string)$node->Status;
992
  $dist['time'] = strtotime((string)$node->LastModifiedTime);
993
  $dist['domain'] = (string)$node->DomainName;
994
  }
 
995
  if (isset($node->CallerReference))
996
  $dist['callerReference'] = (string)$node->CallerReference;
997
- if (isset($node->Comment))
998
- $dist['comment'] = (string)$node->Comment;
999
- if (isset($node->Enabled, $node->Origin)) {
1000
- $dist['origin'] = (string)$node->Origin;
1001
  $dist['enabled'] = (string)$node->Enabled == 'true' ? true : false;
1002
- } elseif (isset($node->DistributionConfig)) {
1003
- $dist = array_merge($dist, self::__parseCloudFrontDistributionConfig($node->DistributionConfig));
1004
- }
1005
- if (isset($node->CNAME)) {
1006
- $dist['cnames'] = array();
1007
- foreach ($node->CNAME as $cname) $dist['cnames'][(string)$cname] = (string)$cname;
 
 
1008
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1009
  return $dist;
1010
  }
1011
 
@@ -1017,14 +1571,17 @@ class S3 {
1017
  * @param object &$rest S3Request instance
1018
  * @return object
1019
  */
1020
- private static function __getCloudFrontResponse(&$rest) {
 
1021
  $rest->getResponse();
1022
  if ($rest->response->error === false && isset($rest->response->body) &&
1023
- is_string($rest->response->body) && substr($rest->response->body, 0, 5) == '<?xml') {
 
1024
  $rest->response->body = simplexml_load_string($rest->response->body);
1025
  // Grab CloudFront errors
1026
  if (isset($rest->response->body->Error, $rest->response->body->Error->Code,
1027
- $rest->response->body->Error->Message)) {
 
1028
  $rest->response->error = array(
1029
  'code' => (string)$rest->response->body->Error->Code,
1030
  'message' => (string)$rest->response->body->Error->Message
@@ -1043,13 +1600,16 @@ class S3 {
1043
  * @param string &$file File path
1044
  * @return string
1045
  */
1046
- public static function __getMimeType(&$file) {
 
1047
  $type = false;
1048
  // Fileinfo documentation says fileinfo_open() will use the
1049
  // MAGIC env var for the magic file
1050
  if (extension_loaded('fileinfo') && isset($_ENV['MAGIC']) &&
1051
- ($finfo = finfo_open(FILEINFO_MIME, $_ENV['MAGIC'])) !== false) {
1052
- if (($type = finfo_file($finfo, $file)) !== false) {
 
 
1053
  // Remove the charset and grab the last content-type
1054
  $type = explode(' ', str_replace('; charset=', ';charset=', $type));
1055
  $type = array_pop($type);
@@ -1091,7 +1651,8 @@ class S3 {
1091
  * @param string $string String to sign
1092
  * @return string
1093
  */
1094
- public static function __getSignature($string) {
 
1095
  return 'AWS '.self::$__accessKey.':'.self::__getHash($string);
1096
  }
1097
 
@@ -1105,7 +1666,8 @@ class S3 {
1105
  * @param string $string String to sign
1106
  * @return string
1107
  */
1108
- private static function __getHash($string) {
 
1109
  return base64_encode(extension_loaded('hash') ?
1110
  hash_hmac('sha1', $string, self::$__secretKey, true) : pack('H*', sha1(
1111
  (str_pad(self::$__secretKey, 64, chr(0x00)) ^ (str_repeat(chr(0x5c), 64))) .
@@ -1115,8 +1677,9 @@ class S3 {
1115
 
1116
  }
1117
 
1118
- final class S3Request {
1119
- private $verb, $bucket, $uri, $resource = '', $parameters = array(),
 
1120
  $amzHeaders = array(), $headers = array(
1121
  'Host' => '', 'Date' => '', 'Content-MD5' => '', 'Content-Type' => ''
1122
  );
@@ -1131,21 +1694,42 @@ final class S3Request {
1131
  * @param string $uri Object URI
1132
  * @return mixed
1133
  */
1134
- function __construct($verb, $bucket = '', $uri = '', $defaultHost = 's3.amazonaws.com') {
 
 
1135
  $this->verb = $verb;
1136
- $this->bucket = strtolower($bucket);
1137
  $this->uri = $uri !== '' ? '/'.str_replace('%2F', '/', rawurlencode($uri)) : '/';
1138
 
1139
- if ($this->bucket !== '') {
1140
- $this->headers['Host'] = $this->bucket.'.'.$defaultHost;
1141
- $this->resource = '/'.$this->bucket.$this->uri;
1142
- } else {
1143
- $this->headers['Host'] = $defaultHost;
1144
- //$this->resource = strlen($this->uri) > 1 ? '/'.$this->bucket.$this->uri : $this->uri;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1145
  $this->resource = $this->uri;
1146
  }
1147
- $this->headers['Date'] = gmdate('D, d M Y H:i:s T');
1148
 
 
 
1149
  $this->response = new STDClass;
1150
  $this->response->error = false;
1151
  }
@@ -1158,7 +1742,8 @@ final class S3Request {
1158
  * @param string $value Value
1159
  * @return void
1160
  */
1161
- public function setParameter($key, $value) {
 
1162
  $this->parameters[$key] = $value;
1163
  }
1164
 
@@ -1170,7 +1755,8 @@ final class S3Request {
1170
  * @param string $value Value
1171
  * @return void
1172
  */
1173
- public function setHeader($key, $value) {
 
1174
  $this->headers[$key] = $value;
1175
  }
1176
 
@@ -1182,7 +1768,8 @@ final class S3Request {
1182
  * @param string $value Value
1183
  * @return void
1184
  */
1185
- public function setAmzHeader($key, $value) {
 
1186
  $this->amzHeaders[$key] = $value;
1187
  }
1188
 
@@ -1192,13 +1779,14 @@ final class S3Request {
1192
  *
1193
  * @return object | false
1194
  */
1195
- public function getResponse() {
 
1196
  $query = '';
1197
- if (sizeof($this->parameters) > 0) {
 
1198
  $query = substr($this->uri, -1) !== '?' ? '?' : '&';
1199
  foreach ($this->parameters as $var => $value)
1200
  if ($value == null || $value == '') $query .= $var.'&';
1201
- // Parameters should be encoded (thanks Sean O'Dea)
1202
  else $query .= $var.'='.rawurlencode($value).'&';
1203
  $query = substr($query, 0, -1);
1204
  $this->uri .= $query;
@@ -1206,24 +1794,39 @@ final class S3Request {
1206
  if (array_key_exists('acl', $this->parameters) ||
1207
  array_key_exists('location', $this->parameters) ||
1208
  array_key_exists('torrent', $this->parameters) ||
 
1209
  array_key_exists('logging', $this->parameters))
1210
  $this->resource .= $query;
1211
  }
1212
- $url = ((S3::$useSSL && extension_loaded('openssl')) ?
1213
- 'https://':'http://').$this->headers['Host'].$this->uri;
1214
- //var_dump($this->bucket, $this->uri, $this->resource, $url);
1215
 
1216
  // Basic setup
1217
  $curl = curl_init();
1218
  curl_setopt($curl, CURLOPT_USERAGENT, 'S3/php');
1219
 
1220
- if (S3::$useSSL) {
1221
- curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 1);
1222
- curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 1);
 
 
 
 
 
 
1223
  }
1224
 
1225
  curl_setopt($curl, CURLOPT_URL, $url);
1226
 
 
 
 
 
 
 
 
 
1227
  // Headers
1228
  $headers = array(); $amz = array();
1229
  foreach ($this->amzHeaders as $header => $value)
@@ -1236,17 +1839,29 @@ final class S3Request {
1236
  if (strlen($value) > 0) $amz[] = strtolower($header).':'.$value;
1237
 
1238
  // AMZ headers must be sorted
1239
- if (sizeof($amz) > 0) {
1240
- sort($amz);
 
 
1241
  $amz = "\n".implode("\n", $amz);
1242
  } else $amz = '';
1243
 
1244
- // Authorization string (CloudFront stringToSign should only contain a date)
1245
- $headers[] = 'Authorization: ' . S3::__getSignature(
1246
- $this->headers['Host'] == 'cloudfront.amazonaws.com' ? $this->headers['Date'] :
1247
- $this->verb."\n".$this->headers['Content-MD5']."\n".
1248
- $this->headers['Content-Type']."\n".$this->headers['Date'].$amz."\n".$this->resource
1249
- );
 
 
 
 
 
 
 
 
 
 
1250
 
1251
  curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
1252
  curl_setopt($curl, CURLOPT_HEADER, false);
@@ -1256,20 +1871,23 @@ final class S3Request {
1256
  curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
1257
 
1258
  // Request types
1259
- switch ($this->verb) {
 
1260
  case 'GET': break;
1261
  case 'PUT': case 'POST': // POST only used for CloudFront
1262
- if ($this->fp !== false) {
 
1263
  curl_setopt($curl, CURLOPT_PUT, true);
1264
  curl_setopt($curl, CURLOPT_INFILE, $this->fp);
1265
  if ($this->size >= 0)
1266
  curl_setopt($curl, CURLOPT_INFILESIZE, $this->size);
1267
- } elseif ($this->data !== false) {
 
 
1268
  curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb);
1269
  curl_setopt($curl, CURLOPT_POSTFIELDS, $this->data);
1270
- if ($this->size >= 0)
1271
- curl_setopt($curl, CURLOPT_BUFFERSIZE, $this->size);
1272
- } else
1273
  curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb);
1274
  break;
1275
  case 'HEAD':
@@ -1296,12 +1914,14 @@ final class S3Request {
1296
 
1297
  // Parse body into XML
1298
  if ($this->response->error === false && isset($this->response->headers['type']) &&
1299
- $this->response->headers['type'] == 'application/xml' && isset($this->response->body)) {
 
1300
  $this->response->body = simplexml_load_string($this->response->body);
1301
 
1302
  // Grab S3 errors
1303
- if (!in_array($this->response->code, array(200, 204)) &&
1304
- isset($this->response->body->Code, $this->response->body->Message)) {
 
1305
  $this->response->error = array(
1306
  'code' => (string)$this->response->body->Code,
1307
  'message' => (string)$this->response->body->Message
@@ -1318,6 +1938,24 @@ final class S3Request {
1318
  return $this->response;
1319
  }
1320
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1321
 
1322
  /**
1323
  * CURL write callback
@@ -1326,8 +1964,9 @@ final class S3Request {
1326
  * @param string &$data Data
1327
  * @return integer
1328
  */
1329
- private function __responseWriteCallback(&$curl, &$data) {
1330
- if ($this->response->code == 200 && $this->fp !== false)
 
1331
  return fwrite($this->fp, $data);
1332
  else
1333
  $this->response->body .= $data;
@@ -1335,6 +1974,23 @@ final class S3Request {
1335
  }
1336
 
1337
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1338
  /**
1339
  * CURL header callback
1340
  *
@@ -1342,12 +1998,16 @@ final class S3Request {
1342
  * @param string &$data Data
1343
  * @return integer
1344
  */
1345
- private function __responseHeaderCallback(&$curl, &$data) {
 
1346
  if (($strlen = strlen($data)) <= 2) return $strlen;
1347
  if (substr($data, 0, 4) == 'HTTP')
1348
  $this->response->code = (int)substr($data, 9, 3);
1349
- else {
1350
- list($header, $value) = explode(': ', trim($data), 2);
 
 
 
1351
  if ($header == 'Last-Modified')
1352
  $this->response->headers['time'] = strtotime($value);
1353
  elseif ($header == 'Content-Length')
@@ -1357,9 +2017,18 @@ final class S3Request {
1357
  elseif ($header == 'ETag')
1358
  $this->response->headers['hash'] = $value{0} == '"' ? substr($value, 1, -1) : $value;
1359
  elseif (preg_match('/^x-amz-meta-.*$/', $header))
1360
- $this->response->headers[$header] = is_numeric($value) ? (int)$value : $value;
1361
  }
1362
  return $strlen;
1363
  }
1364
 
1365
  }
 
 
 
 
 
 
 
 
 
1
  <?php
2
  /**
3
+ * $Id$
4
  *
5
+ * Copyright (c) 2011, Donovan Schönknecht. All rights reserved.
6
  *
7
  * Redistribution and use in source and binary forms, with or without
8
  * modification, are permitted provided that the following conditions are met:
32
  * Amazon S3 PHP class
33
  *
34
  * @link http://undesigned.org.za/2007/10/22/amazon-s3-php-class
35
+ * @version 0.5.0-dev
36
  */
37
+ class S3
38
+ {
39
  // ACL flags
40
  const ACL_PRIVATE = 'private';
41
  const ACL_PUBLIC_READ = 'public-read';
42
  const ACL_PUBLIC_READ_WRITE = 'public-read-write';
43
  const ACL_AUTHENTICATED_READ = 'authenticated-read';
44
 
45
+ const STORAGE_CLASS_STANDARD = 'STANDARD';
46
+ const STORAGE_CLASS_RRS = 'REDUCED_REDUNDANCY';
47
 
48
+ private static $__accessKey = null; // AWS Access key
49
+ private static $__secretKey = null; // AWS Secret key
50
+ private static $__sslKey = null;
51
+
52
+ public static $endpoint = 's3.amazonaws.com';
53
+ public static $proxy = null;
54
+
55
+ public static $useSSL = false;
56
+ public static $useSSLValidation = true;
57
+ public static $useExceptions = false;
58
+
59
+ // SSL CURL SSL options - only needed if you are experiencing problems with your OpenSSL configuration
60
+ public static $sslKey = null;
61
+ public static $sslCert = null;
62
+ public static $sslCACert = null;
63
+
64
+ private static $__signingKeyPairId = null; // AWS Key Pair ID
65
+ private static $__signingKeyResource = false; // Key resource, freeSigningKey() must be called to clear it from memory
66
 
67
 
68
  /**
73
  * @param boolean $useSSL Enable SSL
74
  * @return void
75
  */
76
+ public function __construct($accessKey = null, $secretKey = null, $useSSL = false, $endpoint = 's3.amazonaws.com')
77
+ {
78
  if ($accessKey !== null && $secretKey !== null)
79
  self::setAuth($accessKey, $secretKey);
80
  self::$useSSL = $useSSL;
81
+ self::$endpoint = $endpoint;
82
  }
83
 
84
 
85
+ /**
86
+ * Set the sertvice endpoint
87
+ *
88
+ * @param string $host Hostname
89
+ * @return void
90
+ */
91
+ public function setEndpoint($host)
92
+ {
93
+ self::$endpoint = $host;
94
+ }
95
+
96
  /**
97
  * Set AWS access key and secret key
98
  *
100
  * @param string $secretKey Secret key
101
  * @return void
102
  */
103
+ public static function setAuth($accessKey, $secretKey)
104
+ {
105
  self::$__accessKey = $accessKey;
106
  self::$__secretKey = $secretKey;
107
  }
108
 
109
 
110
+ /**
111
+ * Check if AWS keys have been set
112
+ *
113
+ * @return boolean
114
+ */
115
+ public static function hasAuth() {
116
+ return (self::$__accessKey !== null && self::$__secretKey !== null);
117
+ }
118
+
119
+
120
+ /**
121
+ * Set SSL on or off
122
+ *
123
+ * @param boolean $enabled SSL enabled
124
+ * @param boolean $validate SSL certificate validation
125
+ * @return void
126
+ */
127
+ public static function setSSL($enabled, $validate = true)
128
+ {
129
+ self::$useSSL = $enabled;
130
+ self::$useSSLValidation = $validate;
131
+ }
132
+
133
+
134
+ /**
135
+ * Set SSL client certificates (experimental)
136
+ *
137
+ * @param string $sslCert SSL client certificate
138
+ * @param string $sslKey SSL client key
139
+ * @param string $sslCACert SSL CA cert (only required if you are having problems with your system CA cert)
140
+ * @return void
141
+ */
142
+ public static function setSSLAuth($sslCert = null, $sslKey = null, $sslCACert = null)
143
+ {
144
+ self::$sslCert = $sslCert;
145
+ self::$sslKey = $sslKey;
146
+ self::$sslCACert = $sslCACert;
147
+ }
148
+
149
+
150
+ /**
151
+ * Set proxy information
152
+ *
153
+ * @param string $host Proxy hostname and port (localhost:1234)
154
+ * @param string $user Proxy username
155
+ * @param string $pass Proxy password
156
+ * @param constant $type CURL proxy type
157
+ * @return void
158
+ */
159
+ public static function setProxy($host, $user = null, $pass = null, $type = CURLPROXY_SOCKS5)
160
+ {
161
+ self::$proxy = array('host' => $host, 'type' => $type, 'user' => null, 'pass' => 'null');
162
+ }
163
+
164
+
165
+ /**
166
+ * Set the error mode to exceptions
167
+ *
168
+ * @param boolean $enabled Enable exceptions
169
+ * @return void
170
+ */
171
+ public static function setExceptions($enabled = true)
172
+ {
173
+ self::$useExceptions = $enabled;
174
+ }
175
+
176
+
177
+ /**
178
+ * Set signing key
179
+ *
180
+ * @param string $keyPairId AWS Key Pair ID
181
+ * @param string $signingKey Private Key
182
+ * @param boolean $isFile Load private key from file, set to false to load string
183
+ * @return boolean
184
+ */
185
+ public static function setSigningKey($keyPairId, $signingKey, $isFile = true)
186
+ {
187
+ self::$__signingKeyPairId = $keyPairId;
188
+ if ((self::$__signingKeyResource = openssl_pkey_get_private($isFile ?
189
+ file_get_contents($signingKey) : $signingKey)) !== false) return true;
190
+ self::__triggerError('S3::setSigningKey(): Unable to open load private key: '.$signingKey, __FILE__, __LINE__);
191
+ return false;
192
+ }
193
+
194
+
195
+ /**
196
+ * Free signing key from memory, MUST be called if you are using setSigningKey()
197
+ *
198
+ * @return void
199
+ */
200
+ public static function freeSigningKey()
201
+ {
202
+ if (self::$__signingKeyResource !== false)
203
+ openssl_free_key(self::$__signingKeyResource);
204
+ }
205
+
206
+
207
+ /**
208
+ * Internal error handler
209
+ *
210
+ * @internal Internal error handler
211
+ * @param string $message Error message
212
+ * @param string $file Filename
213
+ * @param integer $line Line number
214
+ * @param integer $code Error code
215
+ * @return void
216
+ */
217
+ private static function __triggerError($message, $file, $line, $code = 0)
218
+ {
219
+ if (self::$useExceptions)
220
+ throw new S3Exception($message, $file, $line, $code);
221
+ else
222
+ trigger_error($message, E_USER_WARNING);
223
+ }
224
+
225
+
226
  /**
227
  * Get a list of buckets
228
  *
229
  * @param boolean $detailed Returns detailed bucket list when true
230
  * @return array | false
231
  */
232
+ public static function listBuckets($detailed = false)
233
+ {
234
+ $rest = new S3Request('GET', '', '', self::$endpoint);
235
  $rest = $rest->getResponse();
236
  if ($rest->error === false && $rest->code !== 200)
237
  $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
238
+ if ($rest->error !== false)
239
+ {
240
+ self::__triggerError(sprintf("S3::listBuckets(): [%s] %s", $rest->error['code'],
241
+ $rest->error['message']), __FILE__, __LINE__);
242
  return false;
243
  }
244
  $results = array();
245
  if (!isset($rest->body->Buckets)) return $results;
246
 
247
+ if ($detailed)
248
+ {
249
  if (isset($rest->body->Owner, $rest->body->Owner->ID, $rest->body->Owner->DisplayName))
250
  $results['owner'] = array(
251
  'id' => (string)$rest->body->Owner->ID, 'name' => (string)$rest->body->Owner->ID
275
  * @param boolean $returnCommonPrefixes Set to true to return CommonPrefixes
276
  * @return array | false
277
  */
278
+ public static function getBucket($bucket, $prefix = null, $marker = null, $maxKeys = null, $delimiter = null, $returnCommonPrefixes = false)
279
+ {
280
+ $rest = new S3Request('GET', $bucket, '', self::$endpoint);
281
+ if ($maxKeys == 0) $maxKeys = null;
282
  if ($prefix !== null && $prefix !== '') $rest->setParameter('prefix', $prefix);
283
  if ($marker !== null && $marker !== '') $rest->setParameter('marker', $marker);
284
  if ($maxKeys !== null && $maxKeys !== '') $rest->setParameter('max-keys', $maxKeys);
286
  $response = $rest->getResponse();
287
  if ($response->error === false && $response->code !== 200)
288
  $response->error = array('code' => $response->code, 'message' => 'Unexpected HTTP status');
289
+ if ($response->error !== false)
290
+ {
291
+ self::__triggerError(sprintf("S3::getBucket(): [%s] %s",
292
+ $response->error['code'], $response->error['message']), __FILE__, __LINE__);
293
  return false;
294
  }
295
 
297
 
298
  $nextMarker = null;
299
  if (isset($response->body, $response->body->Contents))
300
+ foreach ($response->body->Contents as $c)
301
+ {
302
  $results[(string)$c->Key] = array(
303
  'name' => (string)$c->Key,
304
  'time' => strtotime((string)$c->LastModified),
320
 
321
  // Loop through truncated results if maxKeys isn't specified
322
  if ($maxKeys == null && $nextMarker !== null && (string)$response->body->IsTruncated == 'true')
323
+ do
324
+ {
325
+ $rest = new S3Request('GET', $bucket, '', self::$endpoint);
326
  if ($prefix !== null && $prefix !== '') $rest->setParameter('prefix', $prefix);
327
  $rest->setParameter('marker', $nextMarker);
328
  if ($delimiter !== null && $delimiter !== '') $rest->setParameter('delimiter', $delimiter);
329
 
330
+ if (($response = $rest->getResponse()) == false || $response->code !== 200) break;
331
 
332
  if (isset($response->body, $response->body->Contents))
333
+ foreach ($response->body->Contents as $c)
334
+ {
335
  $results[(string)$c->Key] = array(
336
  'name' => (string)$c->Key,
337
  'time' => strtotime((string)$c->LastModified),
362
  * @param string $location Set as "EU" to create buckets hosted in Europe
363
  * @return boolean
364
  */
365
+ public static function putBucket($bucket, $acl = self::ACL_PRIVATE, $location = false)
366
+ {
367
+ $rest = new S3Request('PUT', $bucket, '', self::$endpoint);
368
  $rest->setAmzHeader('x-amz-acl', $acl);
369
 
370
+ if ($location !== false)
371
+ {
372
  $dom = new DOMDocument;
373
  $createBucketConfiguration = $dom->createElement('CreateBucketConfiguration');
374
+ $locationConstraint = $dom->createElement('LocationConstraint', $location);
375
  $createBucketConfiguration->appendChild($locationConstraint);
376
  $dom->appendChild($createBucketConfiguration);
377
  $rest->data = $dom->saveXML();
382
 
383
  if ($rest->error === false && $rest->code !== 200)
384
  $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
385
+ if ($rest->error !== false)
386
+ {
387
+ self::__triggerError(sprintf("S3::putBucket({$bucket}, {$acl}, {$location}): [%s] %s",
388
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
389
  return false;
390
  }
391
  return true;
398
  * @param string $bucket Bucket name
399
  * @return boolean
400
  */
401
+ public static function deleteBucket($bucket)
402
+ {
403
+ $rest = new S3Request('DELETE', $bucket, '', self::$endpoint);
404
  $rest = $rest->getResponse();
405
  if ($rest->error === false && $rest->code !== 204)
406
  $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
407
+ if ($rest->error !== false)
408
+ {
409
+ self::__triggerError(sprintf("S3::deleteBucket({$bucket}): [%s] %s",
410
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
411
  return false;
412
  }
413
  return true;
421
  * @param mixed $md5sum Use MD5 hash (supply a string if you want to use your own)
422
  * @return array | false
423
  */
424
+ public static function inputFile($file, $md5sum = true)
425
+ {
426
+ if (!file_exists($file) || !is_file($file) || !is_readable($file))
427
+ {
428
+ self::__triggerError('S3::inputFile(): Unable to open input file: '.$file, __FILE__, __LINE__);
429
  return false;
430
  }
431
+ return array('file' => $file, 'size' => filesize($file), 'md5sum' => $md5sum !== false ?
432
+ (is_string($md5sum) ? $md5sum : base64_encode(md5_file($file, true))) : '');
 
433
  }
434
 
435
 
441
  * @param string $md5sum MD5 hash to send (optional)
442
  * @return array | false
443
  */
444
+ public static function inputResource(&$resource, $bufferSize, $md5sum = '')
445
+ {
446
+ if (!is_resource($resource) || $bufferSize < 0)
447
+ {
448
+ self::__triggerError('S3::inputResource(): Invalid resource or buffer size', __FILE__, __LINE__);
449
  return false;
450
  }
451
  $input = array('size' => $bufferSize, 'md5sum' => $md5sum);
463
  * @param constant $acl ACL constant
464
  * @param array $metaHeaders Array of x-amz-meta-* headers
465
  * @param array $requestHeaders Array of request headers or content type as a string
466
+ * @param constant $storageClass Storage class constant
467
  * @return boolean
468
  */
469
+ public static function putObject($input, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD)
470
+ {
471
  if ($input === false) return false;
472
+ $rest = new S3Request('PUT', $bucket, $uri, self::$endpoint);
473
 
474
+ if (!is_array($input)) $input = array(
475
  'data' => $input, 'size' => strlen($input),
476
  'md5sum' => base64_encode(md5($input, true))
477
  );
501
  $input['type'] = $requestHeaders;
502
 
503
  // Content-Type
504
+ if (!isset($input['type']))
505
+ {
506
  if (isset($requestHeaders['Content-Type']))
507
  $input['type'] =& $requestHeaders['Content-Type'];
508
  elseif (isset($input['file']))
511
  $input['type'] = 'application/octet-stream';
512
  }
513
 
514
+ if ($storageClass !== self::STORAGE_CLASS_STANDARD) // Storage class
515
+ $rest->setAmzHeader('x-amz-storage-class', $storageClass);
516
+
517
  // We need to post with Content-Length and Content-Type, MD5 is optional
518
+ if ($rest->size >= 0 && ($rest->fp !== false || $rest->data !== false))
519
+ {
520
  $rest->setHeader('Content-Type', $input['type']);
521
  if (isset($input['md5sum'])) $rest->setHeader('Content-MD5', $input['md5sum']);
522
 
528
 
529
  if ($rest->response->error === false && $rest->response->code !== 200)
530
  $rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status');
531
+ if ($rest->response->error !== false)
532
+ {
533
+ self::__triggerError(sprintf("S3::putObject(): [%s] %s",
534
+ $rest->response->error['code'], $rest->response->error['message']), __FILE__, __LINE__);
535
  return false;
536
  }
537
  return true;
549
  * @param string $contentType Content type
550
  * @return boolean
551
  */
552
+ public static function putObjectFile($file, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = null)
553
+ {
554
  return self::putObject(self::inputFile($file), $bucket, $uri, $acl, $metaHeaders, $contentType);
555
  }
556
 
566
  * @param string $contentType Content type
567
  * @return boolean
568
  */
569
+ public static function putObjectString($string, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = 'text/plain')
570
+ {
571
  return self::putObject($string, $bucket, $uri, $acl, $metaHeaders, $contentType);
572
  }
573
 
580
  * @param mixed $saveTo Filename or resource to write to
581
  * @return mixed
582
  */
583
+ public static function getObject($bucket, $uri, $saveTo = false)
584
+ {
585
+ $rest = new S3Request('GET', $bucket, $uri, self::$endpoint);
586
+ if ($saveTo !== false)
587
+ {
588
  if (is_resource($saveTo))
589
  $rest->fp =& $saveTo;
590
  else
597
 
598
  if ($rest->response->error === false && $rest->response->code !== 200)
599
  $rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status');
600
+ if ($rest->response->error !== false)
601
+ {
602
+ self::__triggerError(sprintf("S3::getObject({$bucket}, {$uri}): [%s] %s",
603
+ $rest->response->error['code'], $rest->response->error['message']), __FILE__, __LINE__);
604
  return false;
605
  }
606
  return $rest->response;
615
  * @param boolean $returnInfo Return response information
616
  * @return mixed | false
617
  */
618
+ public static function getObjectInfo($bucket, $uri, $returnInfo = true)
619
+ {
620
+ $rest = new S3Request('HEAD', $bucket, $uri, self::$endpoint);
621
  $rest = $rest->getResponse();
622
  if ($rest->error === false && ($rest->code !== 200 && $rest->code !== 404))
623
  $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
624
+ if ($rest->error !== false)
625
+ {
626
+ self::__triggerError(sprintf("S3::getObjectInfo({$bucket}, {$uri}): [%s] %s",
627
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
628
  return false;
629
  }
630
  return $rest->code == 200 ? $returnInfo ? $rest->headers : true : false;
641
  * @param constant $acl ACL constant
642
  * @param array $metaHeaders Optional array of x-amz-meta-* headers
643
  * @param array $requestHeaders Optional array of request headers (content type, disposition, etc.)
644
+ * @param constant $storageClass Storage class constant
645
  * @return mixed | false
646
  */
647
+ public static function copyObject($srcBucket, $srcUri, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD)
648
+ {
649
+ $rest = new S3Request('PUT', $bucket, $uri, self::$endpoint);
650
  $rest->setHeader('Content-Length', 0);
651
  foreach ($requestHeaders as $h => $v) $rest->setHeader($h, $v);
652
  foreach ($metaHeaders as $h => $v) $rest->setAmzHeader('x-amz-meta-'.$h, $v);
653
+ if ($storageClass !== self::STORAGE_CLASS_STANDARD) // Storage class
654
+ $rest->setAmzHeader('x-amz-storage-class', $storageClass);
655
  $rest->setAmzHeader('x-amz-acl', $acl);
656
+ $rest->setAmzHeader('x-amz-copy-source', sprintf('/%s/%s', $srcBucket, rawurlencode($srcUri)));
657
  if (sizeof($requestHeaders) > 0 || sizeof($metaHeaders) > 0)
658
  $rest->setAmzHeader('x-amz-metadata-directive', 'REPLACE');
659
+
660
  $rest = $rest->getResponse();
661
  if ($rest->error === false && $rest->code !== 200)
662
  $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
663
+ if ($rest->error !== false)
664
+ {
665
+ self::__triggerError(sprintf("S3::copyObject({$srcBucket}, {$srcUri}, {$bucket}, {$uri}): [%s] %s",
666
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
667
  return false;
668
  }
669
  return isset($rest->body->LastModified, $rest->body->ETag) ? array(
681
  * @param string $targetPrefix Log prefix (e,g; domain.com-)
682
  * @return boolean
683
  */
684
+ public static function setBucketLogging($bucket, $targetBucket, $targetPrefix = null)
685
+ {
686
  // The S3 log delivery group has to be added to the target bucket's ACP
687
+ if ($targetBucket !== null && ($acp = self::getAccessControlPolicy($targetBucket, '')) !== false)
688
+ {
689
  // Only add permissions to the target bucket when they do not exist
690
  $aclWriteSet = false;
691
  $aclReadSet = false;
692
  foreach ($acp['acl'] as $acl)
693
+ if ($acl['type'] == 'Group' && $acl['uri'] == 'http://acs.amazonaws.com/groups/s3/LogDelivery')
694
+ {
695
  if ($acl['permission'] == 'WRITE') $aclWriteSet = true;
696
  elseif ($acl['permission'] == 'READ_ACP') $aclReadSet = true;
697
  }
707
  $dom = new DOMDocument;
708
  $bucketLoggingStatus = $dom->createElement('BucketLoggingStatus');
709
  $bucketLoggingStatus->setAttribute('xmlns', 'http://s3.amazonaws.com/doc/2006-03-01/');
710
+ if ($targetBucket !== null)
711
+ {
712
  if ($targetPrefix == null) $targetPrefix = $bucket . '-';
713
  $loggingEnabled = $dom->createElement('LoggingEnabled');
714
  $loggingEnabled->appendChild($dom->createElement('TargetBucket', $targetBucket));
718
  }
719
  $dom->appendChild($bucketLoggingStatus);
720
 
721
+ $rest = new S3Request('PUT', $bucket, '', self::$endpoint);
722
  $rest->setParameter('logging', null);
723
  $rest->data = $dom->saveXML();
724
  $rest->size = strlen($rest->data);
726
  $rest = $rest->getResponse();
727
  if ($rest->error === false && $rest->code !== 200)
728
  $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
729
+ if ($rest->error !== false)
730
+ {
731
+ self::__triggerError(sprintf("S3::setBucketLogging({$bucket}, {$targetBucket}): [%s] %s",
732
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
733
  return false;
734
  }
735
  return true;
745
  * @param string $bucket Bucket name
746
  * @return array | false
747
  */
748
+ public static function getBucketLogging($bucket)
749
+ {
750
+ $rest = new S3Request('GET', $bucket, '', self::$endpoint);
751
  $rest->setParameter('logging', null);
752
  $rest = $rest->getResponse();
753
  if ($rest->error === false && $rest->code !== 200)
754
  $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
755
+ if ($rest->error !== false)
756
+ {
757
+ self::__triggerError(sprintf("S3::getBucketLogging({$bucket}): [%s] %s",
758
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
759
  return false;
760
  }
761
  if (!isset($rest->body->LoggingEnabled)) return false; // No logging
772
  * @param string $bucket Bucket name
773
  * @return boolean
774
  */
775
+ public static function disableBucketLogging($bucket)
776
+ {
777
  return self::setBucketLogging($bucket, null);
778
  }
779
 
784
  * @param string $bucket Bucket name
785
  * @return string | false
786
  */
787
+ public static function getBucketLocation($bucket)
788
+ {
789
+ $rest = new S3Request('GET', $bucket, '', self::$endpoint);
790
  $rest->setParameter('location', null);
791
  $rest = $rest->getResponse();
792
  if ($rest->error === false && $rest->code !== 200)
793
  $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
794
+ if ($rest->error !== false)
795
+ {
796
+ self::__triggerError(sprintf("S3::getBucketLocation({$bucket}): [%s] %s",
797
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
798
  return false;
799
  }
800
  return (isset($rest->body[0]) && (string)$rest->body[0] !== '') ? (string)$rest->body[0] : 'US';
809
  * @param array $acp Access Control Policy Data (same as the data returned from getAccessControlPolicy)
810
  * @return boolean
811
  */
812
+ public static function setAccessControlPolicy($bucket, $uri = '', $acp = array())
813
+ {
814
  $dom = new DOMDocument;
815
  $dom->formatOutput = true;
816
  $accessControlPolicy = $dom->createElement('AccessControlPolicy');
822
  $owner->appendChild($dom->createElement('DisplayName', $acp['owner']['name']));
823
  $accessControlPolicy->appendChild($owner);
824
 
825
+ foreach ($acp['acl'] as $g)
826
+ {
827
  $grant = $dom->createElement('Grant');
828
  $grantee = $dom->createElement('Grantee');
829
  $grantee->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
830
+ if (isset($g['id']))
831
+ { // CanonicalUser (DisplayName is omitted)
832
  $grantee->setAttribute('xsi:type', 'CanonicalUser');
833
  $grantee->appendChild($dom->createElement('ID', $g['id']));
834
+ }
835
+ elseif (isset($g['email']))
836
+ { // AmazonCustomerByEmail
837
  $grantee->setAttribute('xsi:type', 'AmazonCustomerByEmail');
838
  $grantee->appendChild($dom->createElement('EmailAddress', $g['email']));
839
+ }
840
+ elseif ($g['type'] == 'Group')
841
+ { // Group
842
  $grantee->setAttribute('xsi:type', 'Group');
843
  $grantee->appendChild($dom->createElement('URI', $g['uri']));
844
  }
850
  $accessControlPolicy->appendChild($accessControlList);
851
  $dom->appendChild($accessControlPolicy);
852
 
853
+ $rest = new S3Request('PUT', $bucket, $uri, self::$endpoint);
854
  $rest->setParameter('acl', null);
855
  $rest->data = $dom->saveXML();
856
  $rest->size = strlen($rest->data);
858
  $rest = $rest->getResponse();
859
  if ($rest->error === false && $rest->code !== 200)
860
  $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
861
+ if ($rest->error !== false)
862
+ {
863
+ self::__triggerError(sprintf("S3::setAccessControlPolicy({$bucket}, {$uri}): [%s] %s",
864
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
865
  return false;
866
  }
867
  return true;
875
  * @param string $uri Object URI
876
  * @return mixed | false
877
  */
878
+ public static function getAccessControlPolicy($bucket, $uri = '')
879
+ {
880
+ $rest = new S3Request('GET', $bucket, $uri, self::$endpoint);
881
  $rest->setParameter('acl', null);
882
  $rest = $rest->getResponse();
883
  if ($rest->error === false && $rest->code !== 200)
884
  $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
885
+ if ($rest->error !== false)
886
+ {
887
+ self::__triggerError(sprintf("S3::getAccessControlPolicy({$bucket}, {$uri}): [%s] %s",
888
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
889
  return false;
890
  }
891
 
892
  $acp = array();
893
+ if (isset($rest->body->Owner, $rest->body->Owner->ID, $rest->body->Owner->DisplayName))
894
  $acp['owner'] = array(
895
  'id' => (string)$rest->body->Owner->ID, 'name' => (string)$rest->body->Owner->DisplayName
896
  );
897
+
898
+ if (isset($rest->body->AccessControlList))
899
+ {
900
  $acp['acl'] = array();
901
+ foreach ($rest->body->AccessControlList->Grant as $grant)
902
+ {
903
+ foreach ($grant->Grantee as $grantee)
904
+ {
905
  if (isset($grantee->ID, $grantee->DisplayName)) // CanonicalUser
906
  $acp['acl'][] = array(
907
  'type' => 'CanonicalUser',
936
  * @param string $uri Object URI
937
  * @return boolean
938
  */
939
+ public static function deleteObject($bucket, $uri)
940
+ {
941
+ $rest = new S3Request('DELETE', $bucket, $uri, self::$endpoint);
942
  $rest = $rest->getResponse();
943
  if ($rest->error === false && $rest->code !== 204)
944
  $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
945
+ if ($rest->error !== false)
946
+ {
947
+ self::__triggerError(sprintf("S3::deleteObject(): [%s] %s",
948
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
949
  return false;
950
  }
951
  return true;
962
  * @param boolean $https Use HTTPS ($hostBucket should be false for SSL verification)
963
  * @return string
964
  */
965
+ public static function getAuthenticatedURL($bucket, $uri, $lifetime, $hostBucket = false, $https = false)
966
+ {
967
  $expires = time() + $lifetime;
968
+ $uri = str_replace(array('%2F', '%2B'), array('/', '+'), rawurlencode($uri));
969
  return sprintf(($https ? 'https' : 'http').'://%s/%s?AWSAccessKeyId=%s&Expires=%u&Signature=%s',
970
+ // $hostBucket ? $bucket : $bucket.'.s3.amazonaws.com', $uri, self::$__accessKey, $expires,
971
+ $hostBucket ? $bucket : 's3.amazonaws.com/'.$bucket, $uri, self::$__accessKey, $expires,
972
  urlencode(self::__getHash("GET\n\n\n{$expires}\n/{$bucket}/{$uri}")));
973
  }
974
 
975
+
976
+ /**
977
+ * Get a CloudFront signed policy URL
978
+ *
979
+ * @param array $policy Policy
980
+ * @return string
981
+ */
982
+ public static function getSignedPolicyURL($policy)
983
+ {
984
+ $data = json_encode($policy);
985
+ $signature = '';
986
+ if (!openssl_sign($data, $signature, self::$__signingKeyResource)) return false;
987
+
988
+ $encoded = str_replace(array('+', '='), array('-', '_', '~'), base64_encode($data));
989
+ $signature = str_replace(array('+', '='), array('-', '_', '~'), base64_encode($signature));
990
+
991
+ $url = $policy['Statement'][0]['Resource'] . '?';
992
+ foreach (array('Policy' => $encoded, 'Signature' => $signature, 'Key-Pair-Id' => self::$__signingKeyPairId) as $k => $v)
993
+ $url .= $k.'='.str_replace('%2F', '/', rawurlencode($v)).'&';
994
+ return substr($url, 0, -1);
995
+ }
996
+
997
+
998
+ /**
999
+ * Get a CloudFront canned policy URL
1000
+ *
1001
+ * @param string $string URL to sign
1002
+ * @param integer $lifetime URL lifetime
1003
+ * @return string
1004
+ */
1005
+ public static function getSignedCannedURL($url, $lifetime)
1006
+ {
1007
+ return self::getSignedPolicyURL(array(
1008
+ 'Statement' => array(
1009
+ array('Resource' => $url, 'Condition' => array(
1010
+ 'DateLessThan' => array('AWS:EpochTime' => time() + $lifetime)
1011
+ ))
1012
+ )
1013
+ ));
1014
+ }
1015
+
1016
+
1017
  /**
1018
  * Get upload POST parameters for form uploads
1019
  *
1028
  * @param boolean $flashVars Includes additional "Filename" variable posted by Flash
1029
  * @return object
1030
  */
1031
+ public static function getHttpUploadPostParams($bucket, $uriPrefix = '', $acl = self::ACL_PRIVATE, $lifetime = 3600,
1032
+ $maxFileSize = 5242880, $successRedirect = "201", $amzHeaders = array(), $headers = array(), $flashVars = false)
1033
+ {
1034
  // Create policy object
1035
  $policy = new stdClass;
1036
  $policy->expiration = gmdate('Y-m-d\TH:i:s\Z', (time() + $lifetime));
1045
  $obj->success_action_redirect = $successRedirect;
1046
  array_push($policy->conditions, $obj);
1047
 
1048
+ if ($acl !== self::ACL_PUBLIC_READ)
1049
+ array_push($policy->conditions, array('eq', '$acl', $acl));
1050
+
1051
  array_push($policy->conditions, array('starts-with', '$key', $uriPrefix));
1052
  if ($flashVars) array_push($policy->conditions, array('starts-with', '$Filename', ''));
1053
  foreach (array_keys($headers) as $headerKey)
1054
  array_push($policy->conditions, array('starts-with', '$'.$headerKey, ''));
1055
+ foreach ($amzHeaders as $headerKey => $headerVal)
1056
+ {
1057
+ $obj = new stdClass;
1058
+ $obj->{$headerKey} = (string)$headerVal;
1059
+ array_push($policy->conditions, $obj);
1060
  }
1061
  array_push($policy->conditions, array('content-length-range', 0, $maxFileSize));
1062
  $policy = base64_encode(str_replace('\/', '/', json_encode($policy)));
1063
+
1064
  // Create parameters
1065
  $params = new stdClass;
1066
  $params->AWSAccessKeyId = self::$__accessKey;
1077
  return $params;
1078
  }
1079
 
1080
+
1081
  /**
1082
  * Create a CloudFront distribution
1083
  *
1085
  * @param boolean $enabled Enabled (true/false)
1086
  * @param array $cnames Array containing CNAME aliases
1087
  * @param string $comment Use the bucket name as the hostname
1088
+ * @param string $defaultRootObject Default root object
1089
+ * @param string $originAccessIdentity Origin access identity
1090
+ * @param array $trustedSigners Array of trusted signers
1091
  * @return array | false
1092
  */
1093
+ public static function createDistribution($bucket, $enabled = true, $cnames = array(), $comment = null, $defaultRootObject = null, $originAccessIdentity = null, $trustedSigners = array())
1094
+ {
1095
+ if (!extension_loaded('openssl'))
1096
+ {
1097
+ self::__triggerError(sprintf("S3::createDistribution({$bucket}, ".(int)$enabled.", [], '$comment'): %s",
1098
+ "CloudFront functionality requires SSL"), __FILE__, __LINE__);
1099
+ return false;
1100
+ }
1101
+ $useSSL = self::$useSSL;
1102
+
1103
  self::$useSSL = true; // CloudFront requires SSL
1104
+ $rest = new S3Request('POST', '', '2010-11-01/distribution', 'cloudfront.amazonaws.com');
1105
+ $rest->data = self::__getCloudFrontDistributionConfigXML(
1106
+ $bucket.'.s3.amazonaws.com',
1107
+ $enabled,
1108
+ (string)$comment,
1109
+ (string)microtime(true),
1110
+ $cnames,
1111
+ $defaultRootObject,
1112
+ $originAccessIdentity,
1113
+ $trustedSigners
1114
+ );
1115
+
1116
  $rest->size = strlen($rest->data);
1117
  $rest->setHeader('Content-Type', 'application/xml');
1118
  $rest = self::__getCloudFrontResponse($rest);
1119
 
1120
+ self::$useSSL = $useSSL;
1121
+
1122
  if ($rest->error === false && $rest->code !== 201)
1123
  $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1124
+ if ($rest->error !== false)
1125
+ {
1126
+ self::__triggerError(sprintf("S3::createDistribution({$bucket}, ".(int)$enabled.", [], '$comment'): [%s] %s",
1127
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
1128
  return false;
1129
  } elseif ($rest->body instanceof SimpleXMLElement)
1130
  return self::__parseCloudFrontDistributionConfig($rest->body);
1138
  * @param string $distributionId Distribution ID from listDistributions()
1139
  * @return array | false
1140
  */
1141
+ public static function getDistribution($distributionId)
1142
+ {
1143
+ if (!extension_loaded('openssl'))
1144
+ {
1145
+ self::__triggerError(sprintf("S3::getDistribution($distributionId): %s",
1146
+ "CloudFront functionality requires SSL"), __FILE__, __LINE__);
1147
+ return false;
1148
+ }
1149
+ $useSSL = self::$useSSL;
1150
+
1151
  self::$useSSL = true; // CloudFront requires SSL
1152
+ $rest = new S3Request('GET', '', '2010-11-01/distribution/'.$distributionId, 'cloudfront.amazonaws.com');
1153
  $rest = self::__getCloudFrontResponse($rest);
1154
 
1155
+ self::$useSSL = $useSSL;
1156
+
1157
  if ($rest->error === false && $rest->code !== 200)
1158
  $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1159
+ if ($rest->error !== false)
1160
+ {
1161
+ self::__triggerError(sprintf("S3::getDistribution($distributionId): [%s] %s",
1162
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
1163
  return false;
1164
+ }
1165
+ elseif ($rest->body instanceof SimpleXMLElement)
1166
+ {
1167
  $dist = self::__parseCloudFrontDistributionConfig($rest->body);
1168
  $dist['hash'] = $rest->headers['hash'];
1169
+ $dist['id'] = $distributionId;
1170
  return $dist;
1171
  }
1172
  return false;
1179
  * @param array $dist Distribution array info identical to output of getDistribution()
1180
  * @return array | false
1181
  */
1182
+ public static function updateDistribution($dist)
1183
+ {
1184
+ if (!extension_loaded('openssl'))
1185
+ {
1186
+ self::__triggerError(sprintf("S3::updateDistribution({$dist['id']}): %s",
1187
+ "CloudFront functionality requires SSL"), __FILE__, __LINE__);
1188
+ return false;
1189
+ }
1190
+
1191
+ $useSSL = self::$useSSL;
1192
+
1193
  self::$useSSL = true; // CloudFront requires SSL
1194
+ $rest = new S3Request('PUT', '', '2010-11-01/distribution/'.$dist['id'].'/config', 'cloudfront.amazonaws.com');
1195
+ $rest->data = self::__getCloudFrontDistributionConfigXML(
1196
+ $dist['origin'],
1197
+ $dist['enabled'],
1198
+ $dist['comment'],
1199
+ $dist['callerReference'],
1200
+ $dist['cnames'],
1201
+ $dist['defaultRootObject'],
1202
+ $dist['originAccessIdentity'],
1203
+ $dist['trustedSigners']
1204
+ );
1205
+
1206
  $rest->size = strlen($rest->data);
1207
  $rest->setHeader('If-Match', $dist['hash']);
1208
  $rest = self::__getCloudFrontResponse($rest);
1209
 
1210
+ self::$useSSL = $useSSL;
1211
+
1212
  if ($rest->error === false && $rest->code !== 200)
1213
  $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1214
+ if ($rest->error !== false)
1215
+ {
1216
+ self::__triggerError(sprintf("S3::updateDistribution({$dist['id']}): [%s] %s",
1217
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
1218
  return false;
1219
  } else {
1220
  $dist = self::__parseCloudFrontDistributionConfig($rest->body);
1231
  * @param array $dist Distribution array info identical to output of getDistribution()
1232
  * @return boolean
1233
  */
1234
+ public static function deleteDistribution($dist)
1235
+ {
1236
+ if (!extension_loaded('openssl'))
1237
+ {
1238
+ self::__triggerError(sprintf("S3::deleteDistribution({$dist['id']}): %s",
1239
+ "CloudFront functionality requires SSL"), __FILE__, __LINE__);
1240
+ return false;
1241
+ }
1242
+
1243
+ $useSSL = self::$useSSL;
1244
+
1245
  self::$useSSL = true; // CloudFront requires SSL
1246
  $rest = new S3Request('DELETE', '', '2008-06-30/distribution/'.$dist['id'], 'cloudfront.amazonaws.com');
1247
  $rest->setHeader('If-Match', $dist['hash']);
1248
  $rest = self::__getCloudFrontResponse($rest);
1249
 
1250
+ self::$useSSL = $useSSL;
1251
+
1252
  if ($rest->error === false && $rest->code !== 204)
1253
  $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1254
+ if ($rest->error !== false)
1255
+ {
1256
+ self::__triggerError(sprintf("S3::deleteDistribution({$dist['id']}): [%s] %s",
1257
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
1258
  return false;
1259
  }
1260
  return true;
1266
  *
1267
  * @return array
1268
  */
1269
+ public static function listDistributions()
1270
+ {
1271
+ if (!extension_loaded('openssl'))
1272
+ {
1273
+ self::__triggerError(sprintf("S3::listDistributions(): [%s] %s",
1274
+ "CloudFront functionality requires SSL"), __FILE__, __LINE__);
1275
+ return false;
1276
+ }
1277
+
1278
+ $useSSL = self::$useSSL;
1279
  self::$useSSL = true; // CloudFront requires SSL
1280
+ $rest = new S3Request('GET', '', '2010-11-01/distribution', 'cloudfront.amazonaws.com');
1281
  $rest = self::__getCloudFrontResponse($rest);
1282
+ self::$useSSL = $useSSL;
1283
 
1284
  if ($rest->error === false && $rest->code !== 200)
1285
  $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1286
+ if ($rest->error !== false)
1287
+ {
1288
+ self::__triggerError(sprintf("S3::listDistributions(): [%s] %s",
1289
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
1290
  return false;
1291
+ }
1292
+ elseif ($rest->body instanceof SimpleXMLElement && isset($rest->body->DistributionSummary))
1293
+ {
1294
  $list = array();
1295
+ if (isset($rest->body->Marker, $rest->body->MaxItems, $rest->body->IsTruncated))
1296
+ {
1297
  //$info['marker'] = (string)$rest->body->Marker;
1298
  //$info['maxItems'] = (int)$rest->body->MaxItems;
1299
  //$info['isTruncated'] = (string)$rest->body->IsTruncated == 'true' ? true : false;
1300
  }
1301
+ foreach ($rest->body->DistributionSummary as $summary)
1302
  $list[(string)$summary->Id] = self::__parseCloudFrontDistributionConfig($summary);
1303
+
1304
+ return $list;
1305
+ }
1306
+ return array();
1307
+ }
1308
+
1309
+ /**
1310
+ * List CloudFront Origin Access Identities
1311
+ *
1312
+ * @return array
1313
+ */
1314
+ public static function listOriginAccessIdentities()
1315
+ {
1316
+ if (!extension_loaded('openssl'))
1317
+ {
1318
+ self::__triggerError(sprintf("S3::listOriginAccessIdentities(): [%s] %s",
1319
+ "CloudFront functionality requires SSL"), __FILE__, __LINE__);
1320
+ return false;
1321
+ }
1322
+
1323
+ self::$useSSL = true; // CloudFront requires SSL
1324
+ $rest = new S3Request('GET', '', '2010-11-01/origin-access-identity/cloudfront', 'cloudfront.amazonaws.com');
1325
+ $rest = self::__getCloudFrontResponse($rest);
1326
+ $useSSL = self::$useSSL;
1327
+
1328
+ if ($rest->error === false && $rest->code !== 200)
1329
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1330
+ if ($rest->error !== false)
1331
+ {
1332
+ trigger_error(sprintf("S3::listOriginAccessIdentities(): [%s] %s",
1333
+ $rest->error['code'], $rest->error['message']), E_USER_WARNING);
1334
+ return false;
1335
+ }
1336
+
1337
+ if (isset($rest->body->CloudFrontOriginAccessIdentitySummary))
1338
+ {
1339
+ $identities = array();
1340
+ foreach ($rest->body->CloudFrontOriginAccessIdentitySummary as $identity)
1341
+ if (isset($identity->S3CanonicalUserId))
1342
+ $identities[(string)$identity->Id] = array('id' => (string)$identity->Id, 's3CanonicalUserId' => (string)$identity->S3CanonicalUserId);
1343
+ return $identities;
1344
+ }
1345
+ return false;
1346
+ }
1347
+
1348
+
1349
+ /**
1350
+ * Invalidate objects in a CloudFront distribution
1351
+ *
1352
+ * Thanks to Martin Lindkvist for S3::invalidateDistribution()
1353
+ *
1354
+ * @param string $distributionId Distribution ID from listDistributions()
1355
+ * @param array $paths Array of object paths to invalidate
1356
+ * @return boolean
1357
+ */
1358
+ public static function invalidateDistribution($distributionId, $paths)
1359
+ {
1360
+ if (!extension_loaded('openssl'))
1361
+ {
1362
+ self::__triggerError(sprintf("S3::invalidateDistribution(): [%s] %s",
1363
+ "CloudFront functionality requires SSL"), __FILE__, __LINE__);
1364
+ return false;
1365
+ }
1366
+
1367
+ $useSSL = self::$useSSL;
1368
+ self::$useSSL = true; // CloudFront requires SSL
1369
+ $rest = new S3Request('POST', '', '2010-08-01/distribution/'.$distributionId.'/invalidation', 'cloudfront.amazonaws.com');
1370
+ $rest->data = self::__getCloudFrontInvalidationBatchXML($paths, (string)microtime(true));
1371
+ $rest->size = strlen($rest->data);
1372
+ $rest = self::__getCloudFrontResponse($rest);
1373
+ self::$useSSL = $useSSL;
1374
+
1375
+ if ($rest->error === false && $rest->code !== 201)
1376
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1377
+ if ($rest->error !== false)
1378
+ {
1379
+ trigger_error(sprintf("S3::invalidate('{$distributionId}',{$paths}): [%s] %s",
1380
+ $rest->error['code'], $rest->error['message']), E_USER_WARNING);
1381
+ return false;
1382
+ }
1383
+ return true;
1384
+ }
1385
+
1386
+
1387
+ /**
1388
+ * Get a InvalidationBatch DOMDocument
1389
+ *
1390
+ * @internal Used to create XML in invalidateDistribution()
1391
+ * @param array $paths Paths to objects to invalidateDistribution
1392
+ * @return string
1393
+ */
1394
+ private static function __getCloudFrontInvalidationBatchXML($paths, $callerReference = '0') {
1395
+ $dom = new DOMDocument('1.0', 'UTF-8');
1396
+ $dom->formatOutput = true;
1397
+ $invalidationBatch = $dom->createElement('InvalidationBatch');
1398
+ foreach ($paths as $path)
1399
+ $invalidationBatch->appendChild($dom->createElement('Path', $path));
1400
+
1401
+ $invalidationBatch->appendChild($dom->createElement('CallerReference', $callerReference));
1402
+ $dom->appendChild($invalidationBatch);
1403
+ return $dom->saveXML();
1404
+ }
1405
+
1406
+
1407
+ /**
1408
+ * List your invalidation batches for invalidateDistribution() in a CloudFront distribution
1409
+ *
1410
+ * http://docs.amazonwebservices.com/AmazonCloudFront/latest/APIReference/ListInvalidation.html
1411
+ * returned array looks like this:
1412
+ * Array
1413
+ * (
1414
+ * [I31TWB0CN9V6XD] => InProgress
1415
+ * [IT3TFE31M0IHZ] => Completed
1416
+ * [I12HK7MPO1UQDA] => Completed
1417
+ * [I1IA7R6JKTC3L2] => Completed
1418
+ * )
1419
+ *
1420
+ * @param string $distributionId Distribution ID from listDistributions()
1421
+ * @return array
1422
+ */
1423
+ public static function getDistributionInvalidationList($distributionId)
1424
+ {
1425
+ if (!extension_loaded('openssl'))
1426
+ {
1427
+ self::__triggerError(sprintf("S3::getDistributionInvalidationList(): [%s] %s",
1428
+ "CloudFront functionality requires SSL"), __FILE__, __LINE__);
1429
+ return false;
1430
+ }
1431
+
1432
+ $useSSL = self::$useSSL;
1433
+ self::$useSSL = true; // CloudFront requires SSL
1434
+ $rest = new S3Request('GET', '', '2010-11-01/distribution/'.$distributionId.'/invalidation', 'cloudfront.amazonaws.com');
1435
+ $rest = self::__getCloudFrontResponse($rest);
1436
+ self::$useSSL = $useSSL;
1437
+
1438
+ if ($rest->error === false && $rest->code !== 200)
1439
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1440
+ if ($rest->error !== false)
1441
+ {
1442
+ trigger_error(sprintf("S3::getDistributionInvalidationList('{$distributionId}'): [%s]",
1443
+ $rest->error['code'], $rest->error['message']), E_USER_WARNING);
1444
+ return false;
1445
+ }
1446
+ elseif ($rest->body instanceof SimpleXMLElement && isset($rest->body->InvalidationSummary))
1447
+ {
1448
+ $list = array();
1449
+ foreach ($rest->body->InvalidationSummary as $summary)
1450
+ $list[(string)$summary->Id] = (string)$summary->Status;
1451
+
1452
  return $list;
1453
  }
1454
  return array();
1458
  /**
1459
  * Get a DistributionConfig DOMDocument
1460
  *
1461
+ * http://docs.amazonwebservices.com/AmazonCloudFront/latest/APIReference/index.html?PutConfig.html
1462
+ *
1463
  * @internal Used to create XML in createDistribution() and updateDistribution()
1464
+ * @param string $bucket S3 Origin bucket
1465
  * @param boolean $enabled Enabled (true/false)
1466
  * @param string $comment Comment to append
1467
  * @param string $callerReference Caller reference
1468
  * @param array $cnames Array of CNAME aliases
1469
+ * @param string $defaultRootObject Default root object
1470
+ * @param string $originAccessIdentity Origin access identity
1471
+ * @param array $trustedSigners Array of trusted signers
1472
  * @return string
1473
  */
1474
+ private static function __getCloudFrontDistributionConfigXML($bucket, $enabled, $comment, $callerReference = '0', $cnames = array(), $defaultRootObject = null, $originAccessIdentity = null, $trustedSigners = array())
1475
+ {
1476
  $dom = new DOMDocument('1.0', 'UTF-8');
1477
  $dom->formatOutput = true;
1478
  $distributionConfig = $dom->createElement('DistributionConfig');
1479
+ $distributionConfig->setAttribute('xmlns', 'http://cloudfront.amazonaws.com/doc/2010-11-01/');
1480
+
1481
+ $origin = $dom->createElement('S3Origin');
1482
+ $origin->appendChild($dom->createElement('DNSName', $bucket));
1483
+ if ($originAccessIdentity !== null) $origin->appendChild($dom->createElement('OriginAccessIdentity', $originAccessIdentity));
1484
+ $distributionConfig->appendChild($origin);
1485
+
1486
+ if ($defaultRootObject !== null) $distributionConfig->appendChild($dom->createElement('DefaultRootObject', $defaultRootObject));
1487
+
1488
  $distributionConfig->appendChild($dom->createElement('CallerReference', $callerReference));
1489
  foreach ($cnames as $cname)
1490
  $distributionConfig->appendChild($dom->createElement('CNAME', $cname));
1491
  if ($comment !== '') $distributionConfig->appendChild($dom->createElement('Comment', $comment));
1492
  $distributionConfig->appendChild($dom->createElement('Enabled', $enabled ? 'true' : 'false'));
1493
+
1494
+ $trusted = $dom->createElement('TrustedSigners');
1495
+ foreach ($trustedSigners as $id => $type)
1496
+ $trusted->appendChild($id !== '' ? $dom->createElement($type, $id) : $dom->createElement($type));
1497
+ $distributionConfig->appendChild($trusted);
1498
+
1499
  $dom->appendChild($distributionConfig);
1500
+ //var_dump($dom->saveXML());
1501
  return $dom->saveXML();
1502
  }
1503
 
1505
  /**
1506
  * Parse a CloudFront distribution config
1507
  *
1508
+ * See http://docs.amazonwebservices.com/AmazonCloudFront/latest/APIReference/index.html?GetDistribution.html
1509
+ *
1510
  * @internal Used to parse the CloudFront DistributionConfig node to an array
1511
  * @param object &$node DOMNode
1512
  * @return array
1513
  */
1514
+ private static function __parseCloudFrontDistributionConfig(&$node)
1515
+ {
1516
+ if (isset($node->DistributionConfig))
1517
+ return self::__parseCloudFrontDistributionConfig($node->DistributionConfig);
1518
+
1519
  $dist = array();
1520
+ if (isset($node->Id, $node->Status, $node->LastModifiedTime, $node->DomainName))
1521
+ {
1522
  $dist['id'] = (string)$node->Id;
1523
  $dist['status'] = (string)$node->Status;
1524
  $dist['time'] = strtotime((string)$node->LastModifiedTime);
1525
  $dist['domain'] = (string)$node->DomainName;
1526
  }
1527
+
1528
  if (isset($node->CallerReference))
1529
  $dist['callerReference'] = (string)$node->CallerReference;
1530
+
1531
+ if (isset($node->Enabled))
 
 
1532
  $dist['enabled'] = (string)$node->Enabled == 'true' ? true : false;
1533
+
1534
+ if (isset($node->S3Origin))
1535
+ {
1536
+ if (isset($node->S3Origin->DNSName))
1537
+ $dist['origin'] = (string)$node->S3Origin->DNSName;
1538
+
1539
+ $dist['originAccessIdentity'] = isset($node->S3Origin->OriginAccessIdentity) ?
1540
+ (string)$node->S3Origin->OriginAccessIdentity : null;
1541
  }
1542
+
1543
+ $dist['defaultRootObject'] = isset($node->DefaultRootObject) ? (string)$node->DefaultRootObject : null;
1544
+
1545
+ $dist['cnames'] = array();
1546
+ if (isset($node->CNAME))
1547
+ foreach ($node->CNAME as $cname)
1548
+ $dist['cnames'][(string)$cname] = (string)$cname;
1549
+
1550
+ $dist['trustedSigners'] = array();
1551
+ if (isset($node->TrustedSigners))
1552
+ foreach ($node->TrustedSigners as $signer)
1553
+ {
1554
+ if (isset($signer->Self))
1555
+ $dist['trustedSigners'][''] = 'Self';
1556
+ elseif (isset($signer->KeyPairId))
1557
+ $dist['trustedSigners'][(string)$signer->KeyPairId] = 'KeyPairId';
1558
+ elseif (isset($signer->AwsAccountNumber))
1559
+ $dist['trustedSigners'][(string)$signer->AwsAccountNumber] = 'AwsAccountNumber';
1560
+ }
1561
+
1562
+ $dist['comment'] = isset($node->Comment) ? (string)$node->Comment : null;
1563
  return $dist;
1564
  }
1565
 
1571
  * @param object &$rest S3Request instance
1572
  * @return object
1573
  */
1574
+ private static function __getCloudFrontResponse(&$rest)
1575
+ {
1576
  $rest->getResponse();
1577
  if ($rest->response->error === false && isset($rest->response->body) &&
1578
+ is_string($rest->response->body) && substr($rest->response->body, 0, 5) == '<?xml')
1579
+ {
1580
  $rest->response->body = simplexml_load_string($rest->response->body);
1581
  // Grab CloudFront errors
1582
  if (isset($rest->response->body->Error, $rest->response->body->Error->Code,
1583
+ $rest->response->body->Error->Message))
1584
+ {
1585
  $rest->response->error = array(
1586
  'code' => (string)$rest->response->body->Error->Code,
1587
  'message' => (string)$rest->response->body->Error->Message
1600
  * @param string &$file File path
1601
  * @return string
1602
  */
1603
+ public static function __getMimeType(&$file)
1604
+ {
1605
  $type = false;
1606
  // Fileinfo documentation says fileinfo_open() will use the
1607
  // MAGIC env var for the magic file
1608
  if (extension_loaded('fileinfo') && isset($_ENV['MAGIC']) &&
1609
+ ($finfo = finfo_open(FILEINFO_MIME, $_ENV['MAGIC'])) !== false)
1610
+ {
1611
+ if (($type = finfo_file($finfo, $file)) !== false)
1612
+ {
1613
  // Remove the charset and grab the last content-type
1614
  $type = explode(' ', str_replace('; charset=', ';charset=', $type));
1615
  $type = array_pop($type);
1651
  * @param string $string String to sign
1652
  * @return string
1653
  */
1654
+ public static function __getSignature($string)
1655
+ {
1656
  return 'AWS '.self::$__accessKey.':'.self::__getHash($string);
1657
  }
1658
 
1666
  * @param string $string String to sign
1667
  * @return string
1668
  */
1669
+ private static function __getHash($string)
1670
+ {
1671
  return base64_encode(extension_loaded('hash') ?
1672
  hash_hmac('sha1', $string, self::$__secretKey, true) : pack('H*', sha1(
1673
  (str_pad(self::$__secretKey, 64, chr(0x00)) ^ (str_repeat(chr(0x5c), 64))) .
1677
 
1678
  }
1679
 
1680
+ final class S3Request
1681
+ {
1682
+ private $endpoint, $verb, $bucket, $uri, $resource = '', $parameters = array(),
1683
  $amzHeaders = array(), $headers = array(
1684
  'Host' => '', 'Date' => '', 'Content-MD5' => '', 'Content-Type' => ''
1685
  );
1694
  * @param string $uri Object URI
1695
  * @return mixed
1696
  */
1697
+ function __construct($verb, $bucket = '', $uri = '', $endpoint = 's3.amazonaws.com')
1698
+ {
1699
+ $this->endpoint = $endpoint;
1700
  $this->verb = $verb;
1701
+ $this->bucket = $bucket;
1702
  $this->uri = $uri !== '' ? '/'.str_replace('%2F', '/', rawurlencode($uri)) : '/';
1703
 
1704
+ //if ($this->bucket !== '')
1705
+ // $this->resource = '/'.$this->bucket.$this->uri;
1706
+ //else
1707
+ // $this->resource = $this->uri;
1708
+
1709
+ if ($this->bucket !== '')
1710
+ {
1711
+ if ($this->__dnsBucketName($this->bucket))
1712
+ {
1713
+ $this->headers['Host'] = $this->bucket.'.'.$this->endpoint;
1714
+ $this->resource = '/'.$this->bucket.$this->uri;
1715
+ }
1716
+ else
1717
+ {
1718
+ $this->headers['Host'] = $this->endpoint;
1719
+ $this->uri = $this->uri;
1720
+ if ($this->bucket !== '') $this->uri = '/'.$this->bucket.$this->uri;
1721
+ $this->bucket = '';
1722
+ $this->resource = $this->uri;
1723
+ }
1724
+ }
1725
+ else
1726
+ {
1727
+ $this->headers['Host'] = $this->endpoint;
1728
  $this->resource = $this->uri;
1729
  }
 
1730
 
1731
+
1732
+ $this->headers['Date'] = gmdate('D, d M Y H:i:s T');
1733
  $this->response = new STDClass;
1734
  $this->response->error = false;
1735
  }
1742
  * @param string $value Value
1743
  * @return void
1744
  */
1745
+ public function setParameter($key, $value)
1746
+ {
1747
  $this->parameters[$key] = $value;
1748
  }
1749
 
1755
  * @param string $value Value
1756
  * @return void
1757
  */
1758
+ public function setHeader($key, $value)
1759
+ {
1760
  $this->headers[$key] = $value;
1761
  }
1762
 
1768
  * @param string $value Value
1769
  * @return void
1770
  */
1771
+ public function setAmzHeader($key, $value)
1772
+ {
1773
  $this->amzHeaders[$key] = $value;
1774
  }
1775
 
1779
  *
1780
  * @return object | false
1781
  */
1782
+ public function getResponse()
1783
+ {
1784
  $query = '';
1785
+ if (sizeof($this->parameters) > 0)
1786
+ {
1787
  $query = substr($this->uri, -1) !== '?' ? '?' : '&';
1788
  foreach ($this->parameters as $var => $value)
1789
  if ($value == null || $value == '') $query .= $var.'&';
 
1790
  else $query .= $var.'='.rawurlencode($value).'&';
1791
  $query = substr($query, 0, -1);
1792
  $this->uri .= $query;
1794
  if (array_key_exists('acl', $this->parameters) ||
1795
  array_key_exists('location', $this->parameters) ||
1796
  array_key_exists('torrent', $this->parameters) ||
1797
+ array_key_exists('website', $this->parameters) ||
1798
  array_key_exists('logging', $this->parameters))
1799
  $this->resource .= $query;
1800
  }
1801
+ $url = (S3::$useSSL ? 'https://' : 'http://') . ($this->headers['Host'] !== '' ? $this->headers['Host'] : $this->endpoint) . $this->uri;
1802
+
1803
+ //var_dump('bucket: ' . $this->bucket, 'uri: ' . $this->uri, 'resource: ' . $this->resource, 'url: ' . $url);
1804
 
1805
  // Basic setup
1806
  $curl = curl_init();
1807
  curl_setopt($curl, CURLOPT_USERAGENT, 'S3/php');
1808
 
1809
+ if (S3::$useSSL)
1810
+ {
1811
+ // SSL Validation can now be optional for those with broken OpenSSL installations
1812
+ curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, S3::$useSSLValidation ? 1 : 0);
1813
+ curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, S3::$useSSLValidation ? 1 : 0);
1814
+
1815
+ if (S3::$sslKey !== null) curl_setopt($curl, CURLOPT_SSLKEY, S3::$sslKey);
1816
+ if (S3::$sslCert !== null) curl_setopt($curl, CURLOPT_SSLCERT, S3::$sslCert);
1817
+ if (S3::$sslCACert !== null) curl_setopt($curl, CURLOPT_CAINFO, S3::$sslCACert);
1818
  }
1819
 
1820
  curl_setopt($curl, CURLOPT_URL, $url);
1821
 
1822
+ if (S3::$proxy != null && isset(S3::$proxy['host']))
1823
+ {
1824
+ curl_setopt($curl, CURLOPT_PROXY, S3::$proxy['host']);
1825
+ curl_setopt($curl, CURLOPT_PROXYTYPE, S3::$proxy['type']);
1826
+ if (isset(S3::$proxy['user'], S3::$proxy['pass']) && S3::$proxy['user'] != null && S3::$proxy['pass'] != null)
1827
+ curl_setopt($curl, CURLOPT_PROXYUSERPWD, sprintf('%s:%s', S3::$proxy['user'], S3::$proxy['pass']));
1828
+ }
1829
+
1830
  // Headers
1831
  $headers = array(); $amz = array();
1832
  foreach ($this->amzHeaders as $header => $value)
1839
  if (strlen($value) > 0) $amz[] = strtolower($header).':'.$value;
1840
 
1841
  // AMZ headers must be sorted
1842
+ if (sizeof($amz) > 0)
1843
+ {
1844
+ //sort($amz);
1845
+ usort($amz, array(&$this, '__sortMetaHeadersCmp'));
1846
  $amz = "\n".implode("\n", $amz);
1847
  } else $amz = '';
1848
 
1849
+ if (S3::hasAuth())
1850
+ {
1851
+ // Authorization string (CloudFront stringToSign should only contain a date)
1852
+ if ($this->headers['Host'] == 'cloudfront.amazonaws.com')
1853
+ $headers[] = 'Authorization: ' . S3::__getSignature($this->headers['Date']);
1854
+ else
1855
+ {
1856
+ $headers[] = 'Authorization: ' . S3::__getSignature(
1857
+ $this->verb."\n".
1858
+ $this->headers['Content-MD5']."\n".
1859
+ $this->headers['Content-Type']."\n".
1860
+ $this->headers['Date'].$amz."\n".
1861
+ $this->resource
1862
+ );
1863
+ }
1864
+ }
1865
 
1866
  curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
1867
  curl_setopt($curl, CURLOPT_HEADER, false);
1871
  curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
1872
 
1873
  // Request types
1874
+ switch ($this->verb)
1875
+ {
1876
  case 'GET': break;
1877
  case 'PUT': case 'POST': // POST only used for CloudFront
1878
+ if ($this->fp !== false)
1879
+ {
1880
  curl_setopt($curl, CURLOPT_PUT, true);
1881
  curl_setopt($curl, CURLOPT_INFILE, $this->fp);
1882
  if ($this->size >= 0)
1883
  curl_setopt($curl, CURLOPT_INFILESIZE, $this->size);
1884
+ }
1885
+ elseif ($this->data !== false)
1886
+ {
1887
  curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb);
1888
  curl_setopt($curl, CURLOPT_POSTFIELDS, $this->data);
1889
+ }
1890
+ else
 
1891
  curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb);
1892
  break;
1893
  case 'HEAD':
1914
 
1915
  // Parse body into XML
1916
  if ($this->response->error === false && isset($this->response->headers['type']) &&
1917
+ $this->response->headers['type'] == 'application/xml' && isset($this->response->body))
1918
+ {
1919
  $this->response->body = simplexml_load_string($this->response->body);
1920
 
1921
  // Grab S3 errors
1922
+ if (!in_array($this->response->code, array(200, 204, 206)) &&
1923
+ isset($this->response->body->Code, $this->response->body->Message))
1924
+ {
1925
  $this->response->error = array(
1926
  'code' => (string)$this->response->body->Code,
1927
  'message' => (string)$this->response->body->Message
1938
  return $this->response;
1939
  }
1940
 
1941
+ /**
1942
+ * Sort compare for meta headers
1943
+ *
1944
+ * @internal Used to sort x-amz meta headers
1945
+ * @param string $a String A
1946
+ * @param string $b String B
1947
+ * @return integer
1948
+ */
1949
+ private function __sortMetaHeadersCmp($a, $b)
1950
+ {
1951
+ $lenA = strpos($a, ':');
1952
+ $lenB = strpos($b, ':');
1953
+ $minLen = min($lenA, $lenB);
1954
+ $ncmp = strncmp($a, $b, $minLen);
1955
+ if ($lenA == $lenB) return $ncmp;
1956
+ if (0 == $ncmp) return $lenA < $lenB ? -1 : 1;
1957
+ return $ncmp;
1958
+ }
1959
 
1960
  /**
1961
  * CURL write callback
1964
  * @param string &$data Data
1965
  * @return integer
1966
  */
1967
+ private function __responseWriteCallback(&$curl, &$data)
1968
+ {
1969
+ if (in_array($this->response->code, array(200, 206)) && $this->fp !== false)
1970
  return fwrite($this->fp, $data);
1971
  else
1972
  $this->response->body .= $data;
1974
  }
1975
 
1976
 
1977
+ /**
1978
+ * Check DNS conformity
1979
+ *
1980
+ * @param string $bucket Bucket name
1981
+ * @return boolean
1982
+ */
1983
+ private function __dnsBucketName($bucket)
1984
+ {
1985
+ if (strlen($bucket) > 63 || !preg_match("/[^a-z0-9\.-]/", $bucket)) return false;
1986
+ if (strstr($bucket, '-.') !== false) return false;
1987
+ if (strstr($bucket, '..') !== false) return false;
1988
+ if (!preg_match("/^[0-9a-z]/", $bucket)) return false;
1989
+ if (!preg_match("/[0-9a-z]$/", $bucket)) return false;
1990
+ return true;
1991
+ }
1992
+
1993
+
1994
  /**
1995
  * CURL header callback
1996
  *
1998
  * @param string &$data Data
1999
  * @return integer
2000
  */
2001
+ private function __responseHeaderCallback(&$curl, &$data)
2002
+ {
2003
  if (($strlen = strlen($data)) <= 2) return $strlen;
2004
  if (substr($data, 0, 4) == 'HTTP')
2005
  $this->response->code = (int)substr($data, 9, 3);
2006
+ else
2007
+ {
2008
+ $data = trim($data);
2009
+ if (strpos($data, ': ') === false) return $strlen;
2010
+ list($header, $value) = explode(': ', $data, 2);
2011
  if ($header == 'Last-Modified')
2012
  $this->response->headers['time'] = strtotime($value);
2013
  elseif ($header == 'Content-Length')
2017
  elseif ($header == 'ETag')
2018
  $this->response->headers['hash'] = $value{0} == '"' ? substr($value, 1, -1) : $value;
2019
  elseif (preg_match('/^x-amz-meta-.*$/', $header))
2020
+ $this->response->headers[$header] = $value;
2021
  }
2022
  return $strlen;
2023
  }
2024
 
2025
  }
2026
+
2027
+ class S3Exception extends Exception {
2028
+ function __construct($message, $file, $line, $code = 0)
2029
+ {
2030
+ parent::__construct($message, $code);
2031
+ $this->file = $file;
2032
+ $this->line = $line;
2033
+ }
2034
+ }
readme.txt CHANGED
@@ -8,7 +8,7 @@ Donate link: http://david.dw-perspective.org.uk/donate
8
  License: GPLv3 or later
9
 
10
  == Upgrade Notice ==
11
- Full Google Drive support
12
 
13
  == Description ==
14
 
@@ -70,6 +70,9 @@ Contact me! This is a complex plugin and the only way I can ensure it's robust i
70
 
71
  == Changelog ==
72
 
 
 
 
73
  = 0.9.10 - 11/22/2012 =
74
  * Completed basic Google Drive support (thanks to Sorin Iclanzan, code taken from "Backup" plugin under GPLv3+); now supporting uploading, purging and restoring - i.e. full UpdraftPlus functionality
75
  * Licence change to GPLv3+ (from GPLv2+) to allow incorporating Sorin's code
8
  License: GPLv3 or later
9
 
10
  == Upgrade Notice ==
11
+ Updated to latest S3 library (to prepare for future enhancements)
12
 
13
  == Description ==
14
 
70
 
71
  == Changelog ==
72
 
73
+ = 0.9.11 - 12/06/2012 =
74
+ * Updated to latest S3.php library (to allow for future enhancements)
75
+
76
  = 0.9.10 - 11/22/2012 =
77
  * Completed basic Google Drive support (thanks to Sorin Iclanzan, code taken from "Backup" plugin under GPLv3+); now supporting uploading, purging and restoring - i.e. full UpdraftPlus functionality
78
  * Licence change to GPLv3+ (from GPLv2+) to allow incorporating Sorin's code
updraftplus.php CHANGED
@@ -4,7 +4,7 @@ Plugin Name: UpdraftPlus - Backup/Restore
4
  Plugin URI: http://wordpress.org/extend/plugins/updraftplus
5
  Description: Uploads, themes, plugins, and your DB can be automatically backed up to Amazon S3, Google Drive, FTP, or emailed, on separate schedules.
6
  Author: David Anderson.
7
- Version: 0.9.10
8
  Donate link: http://david.dw-perspective.org.uk/donate
9
  License: GPL3
10
  Author URI: http://wordshell.net
@@ -62,7 +62,7 @@ define('UPDRAFT_DEFAULT_OTHERS_EXCLUDE','upgrade,cache,updraft,index.php');
62
 
63
  class UpdraftPlus {
64
 
65
- var $version = '0.9.10';
66
 
67
  var $dbhandle;
68
  var $errors = array();
4
  Plugin URI: http://wordpress.org/extend/plugins/updraftplus
5
  Description: Uploads, themes, plugins, and your DB can be automatically backed up to Amazon S3, Google Drive, FTP, or emailed, on separate schedules.
6
  Author: David Anderson.
7
+ Version: 0.9.11
8
  Donate link: http://david.dw-perspective.org.uk/donate
9
  License: GPL3
10
  Author URI: http://wordshell.net
62
 
63
  class UpdraftPlus {
64
 
65
+ var $version = '0.9.11';
66
 
67
  var $dbhandle;
68
  var $errors = array();