├── README.textile ├── libraries ├── S3_upload.php └── S3.php └── config └── s3.php /README.textile: -------------------------------------------------------------------------------- 1 | h1. Amazon S3 Upload Library for CodeIgniter 2 | 3 | Easily integrate your CI applications to Amazon's Simple Storage Solution with this library. 4 | 5 | h2. Setup 6 | 7 | # Create Amazon S3 account 8 | # Create a bucket for the files 9 | # Get Amazon S3 API key and secret 10 | # Edit config/s3.php with your appropriate settings 11 | # Copy config and library files to your CI installation 12 | 13 | h2. Example Usage 14 | 15 |

16 |   // Load Library
17 |   $this->load->library('s3_upload');
18 |   
19 |   // Upload file
20 |   $sample_file = APPPATH.'public/img/apple.gif';
21 |   $file_url = $this->s3_upload->upload_file($sample_file);
22 | 
23 |   var_dump($file_url);
24 |   // string(56) "https://bucket-name.s3.amazonaws.com/files/apple-561.gif"
25 | 
26 | 27 | h2. References 28 | 29 | * "Original Github repository":https://github.com/psugand/CodeIgniter-S3 30 | * "Original documentation for this class":http://undesigned.org.za/2007/10/22/amazon-s3-php-class/documentation 31 | * "Amazon S3 Documentation":http://docs.amazonwebservices.com/AmazonS3/2006-03-01/ 32 | -------------------------------------------------------------------------------- /libraries/S3_upload.php: -------------------------------------------------------------------------------- 1 | CI =& get_instance(); 13 | $this->CI->load->library('s3'); 14 | 15 | $this->CI->config->load('s3', TRUE); 16 | $s3_config = $this->CI->config->item('s3'); 17 | $this->bucket_name = $s3_config['bucket_name']; 18 | $this->folder_name = $s3_config['folder_name']; 19 | $this->s3_url = $s3_config['s3_url']; 20 | } 21 | 22 | function upload_file($file_path) 23 | { 24 | // generate unique filename 25 | $file = pathinfo($file_path); 26 | $s3_file = $file['filename'].'-'.rand(1000,1).'.'.$file['extension']; 27 | $mime_type = finfo_file(finfo_open(FILEINFO_MIME_TYPE), $file_path); 28 | 29 | $saved = $this->CI->s3->putObjectFile( 30 | $file_path, 31 | $this->bucket_name, 32 | $this->folder_name.$s3_file, 33 | S3::ACL_PUBLIC_READ, 34 | array(), 35 | $mime_type 36 | ); 37 | if ($saved) { 38 | return 'https://'.$this->bucket_name.'.s3.amazonaws.com/'.$this->folder_name.$s3_file; 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /config/s3.php: -------------------------------------------------------------------------------- 1 | initialize($config); 65 | } 66 | 67 | log_message('debug', 'S3 Class Initialized'); 68 | } 69 | 70 | // -------------------------------------------------------------------- 71 | 72 | /** 73 | * Initialize preferences 74 | * 75 | * @access public 76 | * @param array 77 | * @return void 78 | */ 79 | function initialize($config = array()) 80 | { 81 | extract($config); 82 | 83 | if ( ! empty($access_key) AND ! empty($secret_key)) 84 | { 85 | self::setAuth($access_key, $secret_key); 86 | } 87 | 88 | self::$use_ssl = $use_ssl; 89 | self::$verify_peer = $verify_peer; 90 | } 91 | 92 | /** 93 | * Set AWS access key and secret key 94 | * 95 | * @param string $accessKey Access key 96 | * @param string $secretKey Secret key 97 | * @return void 98 | */ 99 | public static function setAuth($accessKey, $secretKey) 100 | { 101 | self::$__access_key = $accessKey; 102 | self::$__secret_key = $secretKey; 103 | } 104 | 105 | /** 106 | * Get a list of buckets 107 | * 108 | * @param boolean $detailed Returns detailed bucket list when true 109 | * @return array | false 110 | */ 111 | public static function listBuckets($detailed = false) 112 | { 113 | $rest = new S3Request('GET', '', ''); 114 | $rest = $rest->getResponse(); 115 | if ($rest->error === false && $rest->code !== 200) 116 | $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); 117 | if ($rest->error !== false) 118 | { 119 | trigger_error(sprintf("S3::listBuckets(): [%s] %s", $rest->error['code'], $rest->error['message']), E_USER_WARNING); 120 | return false; 121 | } 122 | $results = array(); 123 | if (!isset($rest->body->Buckets)) 124 | return $results; 125 | 126 | if ($detailed) 127 | { 128 | if (isset($rest->body->Owner, $rest->body->Owner->ID, $rest->body->Owner->DisplayName)) 129 | $results['owner'] = array( 130 | 'id' => (string) $rest->body->Owner->ID, 'name' => (string) $rest->body->Owner->ID 131 | ); 132 | $results['buckets'] = array(); 133 | foreach ($rest->body->Buckets->Bucket as $b) 134 | $results['buckets'][] = array( 135 | 'name' => (string) $b->Name, 'time' => strtotime((string) $b->CreationDate) 136 | ); 137 | } else 138 | foreach ($rest->body->Buckets->Bucket as $b) 139 | $results[] = (string) $b->Name; 140 | 141 | return $results; 142 | } 143 | 144 | /* 145 | * Get contents for a bucket 146 | * 147 | * If maxKeys is null this method will loop through truncated result sets 148 | * 149 | * @param string $bucket Bucket name 150 | * @param string $prefix Prefix 151 | * @param string $marker Marker (last file listed) 152 | * @param string $maxKeys Max keys (maximum number of keys to return) 153 | * @param string $delimiter Delimiter 154 | * @param boolean $returnCommonPrefixes Set to true to return CommonPrefixes 155 | * @return array | false 156 | */ 157 | 158 | public static function getBucket($bucket, $prefix = null, $marker = null, $maxKeys = null, $delimiter = null, $returnCommonPrefixes = false) 159 | { 160 | $rest = new S3Request('GET', $bucket, ''); 161 | if ($prefix !== null && $prefix !== '') 162 | $rest->setParameter('prefix', $prefix); 163 | if ($marker !== null && $marker !== '') 164 | $rest->setParameter('marker', $marker); 165 | if ($maxKeys !== null && $maxKeys !== '') 166 | $rest->setParameter('max-keys', $maxKeys); 167 | if ($delimiter !== null && $delimiter !== '') 168 | $rest->setParameter('delimiter', $delimiter); 169 | $response = $rest->getResponse(); 170 | if ($response->error === false && $response->code !== 200) 171 | $response->error = array('code' => $response->code, 'message' => 'Unexpected HTTP status'); 172 | if ($response->error !== false) 173 | { 174 | trigger_error(sprintf("S3::getBucket(): [%s] %s", $response->error['code'], $response->error['message']), E_USER_WARNING); 175 | return false; 176 | } 177 | 178 | $results = array(); 179 | 180 | $nextMarker = null; 181 | if (isset($response->body, $response->body->Contents)) 182 | foreach ($response->body->Contents as $c) 183 | { 184 | $results[(string) $c->Key] = array( 185 | 'name' => (string) $c->Key, 186 | 'time' => strtotime((string) $c->LastModified), 187 | 'size' => (int) $c->Size, 188 | 'hash' => substr((string) $c->ETag, 1, -1) 189 | ); 190 | $nextMarker = (string) $c->Key; 191 | } 192 | 193 | if ($returnCommonPrefixes && isset($response->body, $response->body->CommonPrefixes)) 194 | foreach ($response->body->CommonPrefixes as $c) 195 | $results[(string) $c->Prefix] = array('prefix' => (string) $c->Prefix); 196 | 197 | if (isset($response->body, $response->body->IsTruncated) && 198 | (string) $response->body->IsTruncated == 'false') 199 | return $results; 200 | 201 | if (isset($response->body, $response->body->NextMarker)) 202 | $nextMarker = (string) $response->body->NextMarker; 203 | 204 | // Loop through truncated results if maxKeys isn't specified 205 | if ($maxKeys == null && $nextMarker !== null && (string) $response->body->IsTruncated == 'true') 206 | do 207 | { 208 | $rest = new S3Request('GET', $bucket, ''); 209 | if ($prefix !== null && $prefix !== '') 210 | $rest->setParameter('prefix', $prefix); 211 | $rest->setParameter('marker', $nextMarker); 212 | if ($delimiter !== null && $delimiter !== '') 213 | $rest->setParameter('delimiter', $delimiter); 214 | 215 | if (($response = $rest->getResponse(true)) == false || $response->code !== 200) 216 | break; 217 | 218 | if (isset($response->body, $response->body->Contents)) 219 | foreach ($response->body->Contents as $c) 220 | { 221 | $results[(string) $c->Key] = array( 222 | 'name' => (string) $c->Key, 223 | 'time' => strtotime((string) $c->LastModified), 224 | 'size' => (int) $c->Size, 225 | 'hash' => substr((string) $c->ETag, 1, -1) 226 | ); 227 | $nextMarker = (string) $c->Key; 228 | } 229 | 230 | if ($returnCommonPrefixes && isset($response->body, $response->body->CommonPrefixes)) 231 | foreach ($response->body->CommonPrefixes as $c) 232 | $results[(string) $c->Prefix] = array('prefix' => (string) $c->Prefix); 233 | 234 | if (isset($response->body, $response->body->NextMarker)) 235 | $nextMarker = (string) $response->body->NextMarker; 236 | } while ($response !== false && (string) $response->body->IsTruncated == 'true'); 237 | 238 | return $results; 239 | } 240 | 241 | /** 242 | * Put a bucket 243 | * 244 | * @param string $bucket Bucket name 245 | * @param constant $acl ACL flag 246 | * @param string $location Set as "EU" to create buckets hosted in Europe 247 | * @return boolean 248 | */ 249 | public static function putBucket($bucket, $acl = self::ACL_PRIVATE, $location = false) 250 | { 251 | $rest = new S3Request('PUT', $bucket, ''); 252 | $rest->setAmzHeader('x-amz-acl', $acl); 253 | 254 | if ($location !== false) 255 | { 256 | $dom = new DOMDocument; 257 | $createBucketConfiguration = $dom->createElement('CreateBucketConfiguration'); 258 | $locationConstraint = $dom->createElement('LocationConstraint', strtolower($location)); 259 | $createBucketConfiguration->appendChild($locationConstraint); 260 | $dom->appendChild($createBucketConfiguration); 261 | $rest->data = $dom->saveXML(); 262 | $rest->size = strlen($rest->data); 263 | $rest->setHeader('Content-Type', 'application/xml'); 264 | } 265 | $rest = $rest->getResponse(); 266 | 267 | if ($rest->error === false && $rest->code !== 200) 268 | $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); 269 | if ($rest->error !== false) 270 | { 271 | trigger_error(sprintf("S3::putBucket({$bucket}, {$acl}, {$location}): [%s] %s", 272 | $rest->error['code'], $rest->error['message']), E_USER_WARNING); 273 | return false; 274 | } 275 | return true; 276 | } 277 | 278 | /** 279 | * Delete an empty bucket 280 | * 281 | * @param string $bucket Bucket name 282 | * @return boolean 283 | */ 284 | public static function deleteBucket($bucket) 285 | { 286 | $rest = new S3Request('DELETE', $bucket); 287 | $rest = $rest->getResponse(); 288 | if ($rest->error === false && $rest->code !== 204) 289 | $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); 290 | if ($rest->error !== false) 291 | { 292 | trigger_error(sprintf("S3::deleteBucket({$bucket}): [%s] %s", 293 | $rest->error['code'], $rest->error['message']), E_USER_WARNING); 294 | return false; 295 | } 296 | return true; 297 | } 298 | 299 | /** 300 | * Create input info array for putObject() 301 | * 302 | * @param string $file Input file 303 | * @param mixed $md5sum Use MD5 hash (supply a string if you want to use your own) 304 | * @return array | false 305 | */ 306 | public static function inputFile($file, $md5sum = true) 307 | { 308 | if (!file_exists($file) || !is_file($file) || !is_readable($file)) 309 | { 310 | trigger_error('S3::inputFile(): Unable to open input file: ' . $file, E_USER_WARNING); 311 | return false; 312 | } 313 | return array('file' => $file, 'size' => filesize($file), 314 | 'md5sum' => $md5sum !== false ? (is_string($md5sum) ? $md5sum : 315 | base64_encode(md5_file($file, true))) : ''); 316 | } 317 | 318 | /** 319 | * Create input array info for putObject() with a resource 320 | * 321 | * @param string $resource Input resource to read from 322 | * @param integer $bufferSize Input byte size 323 | * @param string $md5sum MD5 hash to send (optional) 324 | * @return array | false 325 | */ 326 | public static function inputResource(&$resource, $bufferSize, $md5sum = '') 327 | { 328 | if (!is_resource($resource) || $bufferSize < 0) 329 | { 330 | trigger_error('S3::inputResource(): Invalid resource or buffer size', E_USER_WARNING); 331 | return false; 332 | } 333 | $input = array('size' => $bufferSize, 'md5sum' => $md5sum); 334 | $input['fp'] = & $resource; 335 | return $input; 336 | } 337 | 338 | /** 339 | * Put an object 340 | * 341 | * @param mixed $input Input data 342 | * @param string $bucket Bucket name 343 | * @param string $uri Object URI 344 | * @param constant $acl ACL constant 345 | * @param array $metaHeaders Array of x-amz-meta-* headers 346 | * @param array $requestHeaders Array of request headers or content type as a string 347 | * @param constant $storageClass Storage class constant 348 | * @param constant $serverSideEncryption Server-side encryption 349 | * @return boolean 350 | */ 351 | public static function putObject($input, $bucket, $uri, $acl = self::ACL_PUBLIC_READ, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD, $serverSideEncryption = self::SSE_NONE) 352 | { 353 | if ($input === false) 354 | return false; 355 | $rest = new S3Request('PUT', $bucket, $uri); 356 | 357 | if (is_string($input)) 358 | $input = array( 359 | 'data' => $input, 'size' => strlen($input), 360 | 'md5sum' => base64_encode(md5($input, true)) 361 | ); 362 | 363 | // Data 364 | if (isset($input['fp'])) 365 | $rest->fp = & $input['fp']; 366 | elseif (isset($input['file'])) 367 | $rest->fp = @fopen($input['file'], 'rb'); 368 | elseif (isset($input['data'])) 369 | $rest->data = $input['data']; 370 | 371 | // Content-Length (required) 372 | if (isset($input['size']) && $input['size'] >= 0) 373 | $rest->size = $input['size']; 374 | else 375 | { 376 | if (isset($input['file'])) 377 | $rest->size = filesize($input['file']); 378 | elseif (isset($input['data'])) 379 | $rest->size = strlen($input['data']); 380 | } 381 | 382 | // Custom request headers (Content-Type, Content-Disposition, Content-Encoding) 383 | if (is_array($requestHeaders)) 384 | foreach ($requestHeaders as $h => $v) 385 | $rest->setHeader($h, $v); 386 | elseif (is_string($requestHeaders)) // Support for legacy contentType parameter 387 | $input['type'] = $requestHeaders; 388 | 389 | // Content-Type 390 | if (!isset($input['type'])) 391 | { 392 | if (isset($requestHeaders['Content-Type'])) 393 | $input['type'] = & $requestHeaders['Content-Type']; 394 | elseif (isset($input['file'])) 395 | $input['type'] = self::__getMimeType($input['file']); 396 | else 397 | $input['type'] = 'application/octet-stream'; 398 | } 399 | 400 | if ($storageClass !== self::STORAGE_CLASS_STANDARD) // Storage class 401 | $rest->setAmzHeader('x-amz-storage-class', $storageClass); 402 | 403 | if ($serverSideEncryption !== self::SSE_NONE) // Server-side encryption 404 | $rest->setAmzHeader('x-amz-server-side-encryption', $serverSideEncryption); 405 | 406 | // We need to post with Content-Length and Content-Type, MD5 is optional 407 | if ($rest->size >= 0 && ($rest->fp !== false || $rest->data !== false)) 408 | { 409 | $rest->setHeader('Content-Type', $input['type']); 410 | if (isset($input['md5sum'])) 411 | $rest->setHeader('Content-MD5', $input['md5sum']); 412 | 413 | $rest->setAmzHeader('x-amz-acl', $acl); 414 | foreach ($metaHeaders as $h => $v) 415 | $rest->setAmzHeader('x-amz-meta-' . $h, $v); 416 | $rest->getResponse(); 417 | } else 418 | $rest->response->error = array('code' => 0, 'message' => 'Missing input parameters'); 419 | 420 | if ($rest->response->error === false && $rest->response->code !== 200) 421 | $rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status'); 422 | if ($rest->response->error !== false) 423 | { 424 | trigger_error(sprintf("S3::putObject(): [%s] %s", $rest->response->error['code'], $rest->response->error['message']), E_USER_WARNING); 425 | return false; 426 | } 427 | return true; 428 | } 429 | 430 | /** 431 | * Put an object from a file (legacy function) 432 | * 433 | * @param string $file Input file path 434 | * @param string $bucket Bucket name 435 | * @param string $uri Object URI 436 | * @param constant $acl ACL constant 437 | * @param array $metaHeaders Array of x-amz-meta-* headers 438 | * @param string $contentType Content type 439 | * @return boolean 440 | */ 441 | public static function putObjectFile($file, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = null) 442 | { 443 | return self::putObject(self::inputFile($file), $bucket, $uri, $acl, $metaHeaders, $contentType); 444 | } 445 | 446 | /** 447 | * Put an object from a string (legacy function) 448 | * 449 | * @param string $string Input data 450 | * @param string $bucket Bucket name 451 | * @param string $uri Object URI 452 | * @param constant $acl ACL constant 453 | * @param array $metaHeaders Array of x-amz-meta-* headers 454 | * @param string $contentType Content type 455 | * @return boolean 456 | */ 457 | public static function putObjectString($string, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = 'text/plain') 458 | { 459 | return self::putObject($string, $bucket, $uri, $acl, $metaHeaders, $contentType); 460 | } 461 | 462 | /** 463 | * Get an object 464 | * 465 | * @param string $bucket Bucket name 466 | * @param string $uri Object URI 467 | * @param mixed $saveTo Filename or resource to write to 468 | * @return mixed 469 | */ 470 | public static function getObject($bucket, $uri, $saveTo = false) 471 | { 472 | $rest = new S3Request('GET', $bucket, $uri); 473 | if ($saveTo !== false) 474 | { 475 | if (is_resource($saveTo)) 476 | $rest->fp = & $saveTo; 477 | else 478 | if (($rest->fp = @fopen($saveTo, 'wb')) !== false) 479 | $rest->file = realpath($saveTo); 480 | else 481 | $rest->response->error = array('code' => 0, 'message' => 'Unable to open save file for writing: ' . $saveTo); 482 | } 483 | if ($rest->response->error === false) 484 | $rest->getResponse(); 485 | 486 | if ($rest->response->error === false && $rest->response->code !== 200) 487 | $rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status'); 488 | if ($rest->response->error !== false) 489 | { 490 | trigger_error(sprintf("S3::getObject({$bucket}, {$uri}): [%s] %s", 491 | $rest->response->error['code'], $rest->response->error['message']), E_USER_WARNING); 492 | return false; 493 | } 494 | return $rest->response; 495 | } 496 | 497 | /** 498 | * Get object information 499 | * 500 | * @param string $bucket Bucket name 501 | * @param string $uri Object URI 502 | * @param boolean $returnInfo Return response information 503 | * @return mixed | false 504 | */ 505 | public static function getObjectInfo($bucket, $uri, $returnInfo = true) 506 | { 507 | $rest = new S3Request('HEAD', $bucket, $uri); 508 | $rest = $rest->getResponse(); 509 | if ($rest->error === false && ($rest->code !== 200 && $rest->code !== 404)) 510 | $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); 511 | if ($rest->error !== false) 512 | { 513 | trigger_error(sprintf("S3::getObjectInfo({$bucket}, {$uri}): [%s] %s", 514 | $rest->error['code'], $rest->error['message']), E_USER_WARNING); 515 | return false; 516 | } 517 | return $rest->code == 200 ? $returnInfo ? $rest->headers : true : false; 518 | } 519 | 520 | /** 521 | * Copy an object 522 | * 523 | * @param string $bucket Source bucket name 524 | * @param string $uri Source object URI 525 | * @param string $bucket Destination bucket name 526 | * @param string $uri Destination object URI 527 | * @param constant $acl ACL constant 528 | * @param array $metaHeaders Optional array of x-amz-meta-* headers 529 | * @param array $requestHeaders Optional array of request headers (content type, disposition, etc.) 530 | * @param constant $storageClass Storage class constant 531 | * @return mixed | false 532 | */ 533 | public static function copyObject($srcBucket, $srcUri, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD) 534 | { 535 | $rest = new S3Request('PUT', $bucket, $uri); 536 | $rest->setHeader('Content-Length', 0); 537 | foreach ($requestHeaders as $h => $v) 538 | $rest->setHeader($h, $v); 539 | foreach ($metaHeaders as $h => $v) 540 | $rest->setAmzHeader('x-amz-meta-' . $h, $v); 541 | if ($storageClass !== self::STORAGE_CLASS_STANDARD) // Storage class 542 | $rest->setAmzHeader('x-amz-storage-class', $storageClass); 543 | $rest->setAmzHeader('x-amz-acl', $acl); 544 | $rest->setAmzHeader('x-amz-copy-source', sprintf('/%s/%s', $srcBucket, $srcUri)); 545 | if (sizeof($requestHeaders) > 0 || sizeof($metaHeaders) > 0) 546 | $rest->setAmzHeader('x-amz-metadata-directive', 'REPLACE'); 547 | $rest = $rest->getResponse(); 548 | if ($rest->error === false && $rest->code !== 200) 549 | $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); 550 | if ($rest->error !== false) 551 | { 552 | trigger_error(sprintf("S3::copyObject({$srcBucket}, {$srcUri}, {$bucket}, {$uri}): [%s] %s", 553 | $rest->error['code'], $rest->error['message']), E_USER_WARNING); 554 | return false; 555 | } 556 | return isset($rest->body->LastModified, $rest->body->ETag) ? array( 557 | 'time' => strtotime((string) $rest->body->LastModified), 558 | 'hash' => substr((string) $rest->body->ETag, 1, -1) 559 | ) : false; 560 | } 561 | 562 | /** 563 | * Set logging for a bucket 564 | * 565 | * @param string $bucket Bucket name 566 | * @param string $targetBucket Target bucket (where logs are stored) 567 | * @param string $targetPrefix Log prefix (e,g; domain.com-) 568 | * @return boolean 569 | */ 570 | public static function setBucketLogging($bucket, $targetBucket, $targetPrefix = null) 571 | { 572 | // The S3 log delivery group has to be added to the target bucket's ACP 573 | if ($targetBucket !== null && ($acp = self::getAccessControlPolicy($targetBucket, '')) !== false) 574 | { 575 | // Only add permissions to the target bucket when they do not exist 576 | $aclWriteSet = false; 577 | $aclReadSet = false; 578 | foreach ($acp['acl'] as $acl) 579 | if ($acl['type'] == 'Group' && $acl['uri'] == 'http://acs.amazonaws.com/groups/s3/LogDelivery') 580 | { 581 | if ($acl['permission'] == 'WRITE') 582 | $aclWriteSet = true; 583 | elseif ($acl['permission'] == 'READ_ACP') 584 | $aclReadSet = true; 585 | } 586 | if (!$aclWriteSet) 587 | $acp['acl'][] = array( 588 | 'type' => 'Group', 'uri' => 'http://acs.amazonaws.com/groups/s3/LogDelivery', 'permission' => 'WRITE' 589 | ); 590 | if (!$aclReadSet) 591 | $acp['acl'][] = array( 592 | 'type' => 'Group', 'uri' => 'http://acs.amazonaws.com/groups/s3/LogDelivery', 'permission' => 'READ_ACP' 593 | ); 594 | if (!$aclReadSet || !$aclWriteSet) 595 | self::setAccessControlPolicy($targetBucket, '', $acp); 596 | } 597 | 598 | $dom = new DOMDocument; 599 | $bucketLoggingStatus = $dom->createElement('BucketLoggingStatus'); 600 | $bucketLoggingStatus->setAttribute('xmlns', 'http://s3.amazonaws.com/doc/2006-03-01/'); 601 | if ($targetBucket !== null) 602 | { 603 | if ($targetPrefix == null) 604 | $targetPrefix = $bucket . '-'; 605 | $loggingEnabled = $dom->createElement('LoggingEnabled'); 606 | $loggingEnabled->appendChild($dom->createElement('TargetBucket', $targetBucket)); 607 | $loggingEnabled->appendChild($dom->createElement('TargetPrefix', $targetPrefix)); 608 | // TODO: Add TargetGrants? 609 | $bucketLoggingStatus->appendChild($loggingEnabled); 610 | } 611 | $dom->appendChild($bucketLoggingStatus); 612 | 613 | $rest = new S3Request('PUT', $bucket, ''); 614 | $rest->setParameter('logging', null); 615 | $rest->data = $dom->saveXML(); 616 | $rest->size = strlen($rest->data); 617 | $rest->setHeader('Content-Type', 'application/xml'); 618 | $rest = $rest->getResponse(); 619 | if ($rest->error === false && $rest->code !== 200) 620 | $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); 621 | if ($rest->error !== false) 622 | { 623 | trigger_error(sprintf("S3::setBucketLogging({$bucket}, {$uri}): [%s] %s", 624 | $rest->error['code'], $rest->error['message']), E_USER_WARNING); 625 | return false; 626 | } 627 | return true; 628 | } 629 | 630 | /** 631 | * Get logging status for a bucket 632 | * 633 | * This will return false if logging is not enabled. 634 | * Note: To enable logging, you also need to grant write access to the log group 635 | * 636 | * @param string $bucket Bucket name 637 | * @return array | false 638 | */ 639 | public static function getBucketLogging($bucket) 640 | { 641 | $rest = new S3Request('GET', $bucket, ''); 642 | $rest->setParameter('logging', null); 643 | $rest = $rest->getResponse(); 644 | if ($rest->error === false && $rest->code !== 200) 645 | $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); 646 | if ($rest->error !== false) 647 | { 648 | trigger_error(sprintf("S3::getBucketLogging({$bucket}): [%s] %s", 649 | $rest->error['code'], $rest->error['message']), E_USER_WARNING); 650 | return false; 651 | } 652 | if (!isset($rest->body->LoggingEnabled)) 653 | return false; // No logging 654 | return array( 655 | 'targetBucket' => (string) $rest->body->LoggingEnabled->TargetBucket, 656 | 'targetPrefix' => (string) $rest->body->LoggingEnabled->TargetPrefix, 657 | ); 658 | } 659 | 660 | /** 661 | * Disable bucket logging 662 | * 663 | * @param string $bucket Bucket name 664 | * @return boolean 665 | */ 666 | public static function disableBucketLogging($bucket) 667 | { 668 | return self::setBucketLogging($bucket, null); 669 | } 670 | 671 | /** 672 | * Get a bucket's location 673 | * 674 | * @param string $bucket Bucket name 675 | * @return string | false 676 | */ 677 | public static function getBucketLocation($bucket) 678 | { 679 | $rest = new S3Request('GET', $bucket, ''); 680 | $rest->setParameter('location', null); 681 | $rest = $rest->getResponse(); 682 | if ($rest->error === false && $rest->code !== 200) 683 | $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); 684 | if ($rest->error !== false) 685 | { 686 | trigger_error(sprintf("S3::getBucketLocation({$bucket}): [%s] %s", 687 | $rest->error['code'], $rest->error['message']), E_USER_WARNING); 688 | return false; 689 | } 690 | return (isset($rest->body[0]) && (string) $rest->body[0] !== '') ? (string) $rest->body[0] : 'US'; 691 | } 692 | 693 | /** 694 | * Set object or bucket Access Control Policy 695 | * 696 | * @param string $bucket Bucket name 697 | * @param string $uri Object URI 698 | * @param array $acp Access Control Policy Data (same as the data returned from getAccessControlPolicy) 699 | * @return boolean 700 | */ 701 | public static function setAccessControlPolicy($bucket, $uri = '', $acp = array()) 702 | { 703 | $dom = new DOMDocument; 704 | $dom->formatOutput = true; 705 | $accessControlPolicy = $dom->createElement('AccessControlPolicy'); 706 | $accessControlList = $dom->createElement('AccessControlList'); 707 | 708 | // It seems the owner has to be passed along too 709 | $owner = $dom->createElement('Owner'); 710 | $owner->appendChild($dom->createElement('ID', $acp['owner']['id'])); 711 | $owner->appendChild($dom->createElement('DisplayName', $acp['owner']['name'])); 712 | $accessControlPolicy->appendChild($owner); 713 | 714 | foreach ($acp['acl'] as $g) 715 | { 716 | $grant = $dom->createElement('Grant'); 717 | $grantee = $dom->createElement('Grantee'); 718 | $grantee->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'); 719 | if (isset($g['id'])) 720 | { // CanonicalUser (DisplayName is omitted) 721 | $grantee->setAttribute('xsi:type', 'CanonicalUser'); 722 | $grantee->appendChild($dom->createElement('ID', $g['id'])); 723 | } 724 | elseif (isset($g['email'])) 725 | { // AmazonCustomerByEmail 726 | $grantee->setAttribute('xsi:type', 'AmazonCustomerByEmail'); 727 | $grantee->appendChild($dom->createElement('EmailAddress', $g['email'])); 728 | } 729 | elseif ($g['type'] == 'Group') 730 | { // Group 731 | $grantee->setAttribute('xsi:type', 'Group'); 732 | $grantee->appendChild($dom->createElement('URI', $g['uri'])); 733 | } 734 | $grant->appendChild($grantee); 735 | $grant->appendChild($dom->createElement('Permission', $g['permission'])); 736 | $accessControlList->appendChild($grant); 737 | } 738 | 739 | $accessControlPolicy->appendChild($accessControlList); 740 | $dom->appendChild($accessControlPolicy); 741 | 742 | $rest = new S3Request('PUT', $bucket, $uri); 743 | $rest->setParameter('acl', null); 744 | $rest->data = $dom->saveXML(); 745 | $rest->size = strlen($rest->data); 746 | $rest->setHeader('Content-Type', 'application/xml'); 747 | $rest = $rest->getResponse(); 748 | if ($rest->error === false && $rest->code !== 200) 749 | $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); 750 | if ($rest->error !== false) 751 | { 752 | trigger_error(sprintf("S3::setAccessControlPolicy({$bucket}, {$uri}): [%s] %s", 753 | $rest->error['code'], $rest->error['message']), E_USER_WARNING); 754 | return false; 755 | } 756 | return true; 757 | } 758 | 759 | /** 760 | * Get object or bucket Access Control Policy 761 | * 762 | * @param string $bucket Bucket name 763 | * @param string $uri Object URI 764 | * @return mixed | false 765 | */ 766 | public static function getAccessControlPolicy($bucket, $uri = '') 767 | { 768 | $rest = new S3Request('GET', $bucket, $uri); 769 | $rest->setParameter('acl', null); 770 | $rest = $rest->getResponse(); 771 | if ($rest->error === false && $rest->code !== 200) 772 | $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); 773 | if ($rest->error !== false) 774 | { 775 | trigger_error(sprintf("S3::getAccessControlPolicy({$bucket}, {$uri}): [%s] %s", 776 | $rest->error['code'], $rest->error['message']), E_USER_WARNING); 777 | return false; 778 | } 779 | 780 | $acp = array(); 781 | if (isset($rest->body->Owner, $rest->body->Owner->ID, $rest->body->Owner->DisplayName)) 782 | { 783 | $acp['owner'] = array( 784 | 'id' => (string) $rest->body->Owner->ID, 'name' => (string) $rest->body->Owner->DisplayName 785 | ); 786 | } 787 | if (isset($rest->body->AccessControlList)) 788 | { 789 | $acp['acl'] = array(); 790 | foreach ($rest->body->AccessControlList->Grant as $grant) 791 | { 792 | foreach ($grant->Grantee as $grantee) 793 | { 794 | if (isset($grantee->ID, $grantee->DisplayName)) // CanonicalUser 795 | $acp['acl'][] = array( 796 | 'type' => 'CanonicalUser', 797 | 'id' => (string) $grantee->ID, 798 | 'name' => (string) $grantee->DisplayName, 799 | 'permission' => (string) $grant->Permission 800 | ); 801 | elseif (isset($grantee->EmailAddress)) // AmazonCustomerByEmail 802 | $acp['acl'][] = array( 803 | 'type' => 'AmazonCustomerByEmail', 804 | 'email' => (string) $grantee->EmailAddress, 805 | 'permission' => (string) $grant->Permission 806 | ); 807 | elseif (isset($grantee->URI)) // Group 808 | $acp['acl'][] = array( 809 | 'type' => 'Group', 810 | 'uri' => (string) $grantee->URI, 811 | 'permission' => (string) $grant->Permission 812 | ); 813 | else 814 | continue; 815 | } 816 | } 817 | } 818 | return $acp; 819 | } 820 | 821 | /** 822 | * Delete an object 823 | * 824 | * @param string $bucket Bucket name 825 | * @param string $uri Object URI 826 | * @return boolean 827 | */ 828 | public static function deleteObject($bucket, $uri) 829 | { 830 | $rest = new S3Request('DELETE', $bucket, $uri); 831 | $rest = $rest->getResponse(); 832 | if ($rest->error === false && $rest->code !== 204) 833 | $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); 834 | if ($rest->error !== false) 835 | { 836 | trigger_error(sprintf("S3::deleteObject(): [%s] %s", 837 | $rest->error['code'], $rest->error['message']), E_USER_WARNING); 838 | return false; 839 | } 840 | return true; 841 | } 842 | 843 | /** 844 | * Get a query string authenticated URL 845 | * 846 | * @param string $bucket Bucket name 847 | * @param string $uri Object URI 848 | * @param integer $lifetime Lifetime in seconds 849 | * @param boolean $hostBucket Use the bucket name as the hostname 850 | * @param boolean $https Use HTTPS ($hostBucket should be false for SSL verification) 851 | * @return string 852 | */ 853 | public static function getAuthenticatedURL($bucket, $uri, $lifetime, $hostBucket = false, $https = false) 854 | { 855 | $expires = time() + $lifetime; 856 | $uri = str_replace('%2F', '/', rawurlencode($uri)); // URI should be encoded (thanks Sean O'Dea) 857 | return sprintf(($https ? 'https' : 'http') . '://%s/%s?AWSAccessKeyId=%s&Expires=%u&Signature=%s', 858 | $hostBucket ? $bucket : $bucket . '.s3.amazonaws.com', $uri, self::$__access_key, $expires, 859 | urlencode(self::__getHash("GET\n\n\n{$expires}\n/{$bucket}/{$uri}"))); 860 | } 861 | 862 | /** 863 | * Get upload POST parameters for form uploads 864 | * 865 | * @param string $bucket Bucket name 866 | * @param string $uriPrefix Object URI prefix 867 | * @param constant $acl ACL constant 868 | * @param integer $lifetime Lifetime in seconds 869 | * @param integer $maxFileSize Maximum filesize in bytes (default 5MB) 870 | * @param string $successRedirect Redirect URL or 200 / 201 status code 871 | * @param array $amzHeaders Array of x-amz-meta-* headers 872 | * @param array $headers Array of request headers or content type as a string 873 | * @param boolean $flashVars Includes additional "Filename" variable posted by Flash 874 | * @return object 875 | */ 876 | public static function getHttpUploadPostParams($bucket, $uriPrefix = '', $acl = self::ACL_PRIVATE, $lifetime = 3600, $maxFileSize = 5242880, $successRedirect = "201", $amzHeaders = array(), $headers = array(), $flashVars = false) 877 | { 878 | // Create policy object 879 | $policy = new stdClass; 880 | $policy->expiration = gmdate('Y-m-d\TH:i:s\Z', (time() + $lifetime)); 881 | $policy->conditions = array(); 882 | $obj = new stdClass; 883 | $obj->bucket = $bucket; 884 | array_push($policy->conditions, $obj); 885 | $obj = new stdClass; 886 | $obj->acl = $acl; 887 | array_push($policy->conditions, $obj); 888 | 889 | $obj = new stdClass; // 200 for non-redirect uploads 890 | if (is_numeric($successRedirect) && in_array((int) $successRedirect, array(200, 201))) 891 | $obj->success_action_status = (string) $successRedirect; 892 | else // URL 893 | $obj->success_action_redirect = $successRedirect; 894 | array_push($policy->conditions, $obj); 895 | 896 | array_push($policy->conditions, array('starts-with', '$key', $uriPrefix)); 897 | if ($flashVars) 898 | array_push($policy->conditions, array('starts-with', '$Filename', '')); 899 | foreach (array_keys($headers) as $headerKey) 900 | array_push($policy->conditions, array('starts-with', '$' . $headerKey, '')); 901 | foreach ($amzHeaders as $headerKey => $headerVal) 902 | { 903 | $obj = new stdClass; 904 | $obj->{$headerKey} = (string) $headerVal; 905 | array_push($policy->conditions, $obj); 906 | } 907 | array_push($policy->conditions, array('content-length-range', 0, $maxFileSize)); 908 | $policy = base64_encode(str_replace('\/', '/', json_encode($policy))); 909 | 910 | // Create parameters 911 | $params = new stdClass; 912 | $params->AWSAccessKeyId = self::$__access_key; 913 | $params->key = $uriPrefix . '${filename}'; 914 | $params->acl = $acl; 915 | $params->policy = $policy; 916 | unset($policy); 917 | $params->signature = self::__getHash($params->policy); 918 | if (is_numeric($successRedirect) && in_array((int) $successRedirect, array(200, 201))) 919 | $params->success_action_status = (string) $successRedirect; 920 | else 921 | $params->success_action_redirect = $successRedirect; 922 | foreach ($headers as $headerKey => $headerVal) 923 | $params->{$headerKey} = (string) $headerVal; 924 | foreach ($amzHeaders as $headerKey => $headerVal) 925 | $params->{$headerKey} = (string) $headerVal; 926 | return $params; 927 | } 928 | 929 | /** 930 | * Create a CloudFront distribution 931 | * 932 | * @param string $bucket Bucket name 933 | * @param boolean $enabled Enabled (true/false) 934 | * @param array $cnames Array containing CNAME aliases 935 | * @param string $comment Use the bucket name as the hostname 936 | * @return array | false 937 | */ 938 | public static function createDistribution($bucket, $enabled = true, $cnames = array(), $comment = '') 939 | { 940 | self::$use_ssl = true; // CloudFront requires SSL 941 | $rest = new S3Request('POST', '', '2008-06-30/distribution', 'cloudfront.amazonaws.com'); 942 | $rest->data = self::__getCloudFrontDistributionConfigXML($bucket . '.s3.amazonaws.com', $enabled, $comment, (string) microtime(true), $cnames); 943 | $rest->size = strlen($rest->data); 944 | $rest->setHeader('Content-Type', 'application/xml'); 945 | $rest = self::__getCloudFrontResponse($rest); 946 | 947 | if ($rest->error === false && $rest->code !== 201) 948 | $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); 949 | if ($rest->error !== false) 950 | { 951 | trigger_error(sprintf("S3::createDistribution({$bucket}, " . (int) $enabled . ", '$comment'): [%s] %s", 952 | $rest->error['code'], $rest->error['message']), E_USER_WARNING); 953 | return false; 954 | } 955 | elseif ($rest->body instanceof SimpleXMLElement) 956 | return self::__parseCloudFrontDistributionConfig($rest->body); 957 | return false; 958 | } 959 | 960 | /** 961 | * Get CloudFront distribution info 962 | * 963 | * @param string $distributionId Distribution ID from listDistributions() 964 | * @return array | false 965 | */ 966 | public static function getDistribution($distributionId) 967 | { 968 | self::$use_ssl = true; // CloudFront requires SSL 969 | $rest = new S3Request('GET', '', '2008-06-30/distribution/' . $distributionId, 'cloudfront.amazonaws.com'); 970 | $rest = self::__getCloudFrontResponse($rest); 971 | 972 | if ($rest->error === false && $rest->code !== 200) 973 | $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); 974 | if ($rest->error !== false) 975 | { 976 | trigger_error(sprintf("S3::getDistribution($distributionId): [%s] %s", 977 | $rest->error['code'], $rest->error['message']), E_USER_WARNING); 978 | return false; 979 | } 980 | elseif ($rest->body instanceof SimpleXMLElement) 981 | { 982 | $dist = self::__parseCloudFrontDistributionConfig($rest->body); 983 | $dist['hash'] = $rest->headers['hash']; 984 | return $dist; 985 | } 986 | return false; 987 | } 988 | 989 | /** 990 | * Update a CloudFront distribution 991 | * 992 | * @param array $dist Distribution array info identical to output of getDistribution() 993 | * @return array | false 994 | */ 995 | public static function updateDistribution($dist) 996 | { 997 | self::$use_ssl = true; // CloudFront requires SSL 998 | $rest = new S3Request('PUT', '', '2008-06-30/distribution/' . $dist['id'] . '/config', 'cloudfront.amazonaws.com'); 999 | $rest->data = self::__getCloudFrontDistributionConfigXML($dist['origin'], $dist['enabled'], $dist['comment'], $dist['callerReference'], $dist['cnames']); 1000 | $rest->size = strlen($rest->data); 1001 | $rest->setHeader('If-Match', $dist['hash']); 1002 | $rest = self::__getCloudFrontResponse($rest); 1003 | 1004 | if ($rest->error === false && $rest->code !== 200) 1005 | $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); 1006 | if ($rest->error !== false) 1007 | { 1008 | trigger_error(sprintf("S3::updateDistribution({$dist['id']}, " . (int) $enabled . ", '$comment'): [%s] %s", 1009 | $rest->error['code'], $rest->error['message']), E_USER_WARNING); 1010 | return false; 1011 | } 1012 | else 1013 | { 1014 | $dist = self::__parseCloudFrontDistributionConfig($rest->body); 1015 | $dist['hash'] = $rest->headers['hash']; 1016 | return $dist; 1017 | } 1018 | return false; 1019 | } 1020 | 1021 | /** 1022 | * Delete a CloudFront distribution 1023 | * 1024 | * @param array $dist Distribution array info identical to output of getDistribution() 1025 | * @return boolean 1026 | */ 1027 | public static function deleteDistribution($dist) 1028 | { 1029 | self::$use_ssl = true; // CloudFront requires SSL 1030 | $rest = new S3Request('DELETE', '', '2008-06-30/distribution/' . $dist['id'], 'cloudfront.amazonaws.com'); 1031 | $rest->setHeader('If-Match', $dist['hash']); 1032 | $rest = self::__getCloudFrontResponse($rest); 1033 | 1034 | if ($rest->error === false && $rest->code !== 204) 1035 | $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); 1036 | if ($rest->error !== false) 1037 | { 1038 | trigger_error(sprintf("S3::deleteDistribution({$dist['id']}): [%s] %s", 1039 | $rest->error['code'], $rest->error['message']), E_USER_WARNING); 1040 | return false; 1041 | } 1042 | return true; 1043 | } 1044 | 1045 | /** 1046 | * Get a list of CloudFront distributions 1047 | * 1048 | * @return array 1049 | */ 1050 | public static function listDistributions() 1051 | { 1052 | self::$use_ssl = true; // CloudFront requires SSL 1053 | $rest = new S3Request('GET', '', '2008-06-30/distribution', 'cloudfront.amazonaws.com'); 1054 | $rest = self::__getCloudFrontResponse($rest); 1055 | 1056 | if ($rest->error === false && $rest->code !== 200) 1057 | $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); 1058 | if ($rest->error !== false) 1059 | { 1060 | trigger_error(sprintf("S3::listDistributions(): [%s] %s", 1061 | $rest->error['code'], $rest->error['message']), E_USER_WARNING); 1062 | return false; 1063 | } 1064 | elseif ($rest->body instanceof SimpleXMLElement && isset($rest->body->DistributionSummary)) 1065 | { 1066 | $list = array(); 1067 | if (isset($rest->body->Marker, $rest->body->MaxItems, $rest->body->IsTruncated)) 1068 | { 1069 | //$info['marker'] = (string)$rest->body->Marker; 1070 | //$info['maxItems'] = (int)$rest->body->MaxItems; 1071 | //$info['isTruncated'] = (string)$rest->body->IsTruncated == 'true' ? true : false; 1072 | } 1073 | foreach ($rest->body->DistributionSummary as $summary) 1074 | { 1075 | $list[(string) $summary->Id] = self::__parseCloudFrontDistributionConfig($summary); 1076 | } 1077 | return $list; 1078 | } 1079 | return array(); 1080 | } 1081 | 1082 | /** 1083 | * Get a DistributionConfig DOMDocument 1084 | * 1085 | * @internal Used to create XML in createDistribution() and updateDistribution() 1086 | * @param string $bucket Origin bucket 1087 | * @param boolean $enabled Enabled (true/false) 1088 | * @param string $comment Comment to append 1089 | * @param string $callerReference Caller reference 1090 | * @param array $cnames Array of CNAME aliases 1091 | * @return string 1092 | */ 1093 | private static function __getCloudFrontDistributionConfigXML($bucket, $enabled, $comment, $callerReference = '0', $cnames = array()) 1094 | { 1095 | $dom = new DOMDocument('1.0', 'UTF-8'); 1096 | $dom->formatOutput = true; 1097 | $distributionConfig = $dom->createElement('DistributionConfig'); 1098 | $distributionConfig->setAttribute('xmlns', 'http://cloudfront.amazonaws.com/doc/2008-06-30/'); 1099 | $distributionConfig->appendChild($dom->createElement('Origin', $bucket)); 1100 | $distributionConfig->appendChild($dom->createElement('CallerReference', $callerReference)); 1101 | foreach ($cnames as $cname) 1102 | $distributionConfig->appendChild($dom->createElement('CNAME', $cname)); 1103 | if ($comment !== '') 1104 | $distributionConfig->appendChild($dom->createElement('Comment', $comment)); 1105 | $distributionConfig->appendChild($dom->createElement('Enabled', $enabled ? 'true' : 'false')); 1106 | $dom->appendChild($distributionConfig); 1107 | return $dom->saveXML(); 1108 | } 1109 | 1110 | /** 1111 | * Parse a CloudFront distribution config 1112 | * 1113 | * @internal Used to parse the CloudFront DistributionConfig node to an array 1114 | * @param object &$node DOMNode 1115 | * @return array 1116 | */ 1117 | private static function __parseCloudFrontDistributionConfig(&$node) 1118 | { 1119 | $dist = array(); 1120 | if (isset($node->Id, $node->Status, $node->LastModifiedTime, $node->DomainName)) 1121 | { 1122 | $dist['id'] = (string) $node->Id; 1123 | $dist['status'] = (string) $node->Status; 1124 | $dist['time'] = strtotime((string) $node->LastModifiedTime); 1125 | $dist['domain'] = (string) $node->DomainName; 1126 | } 1127 | if (isset($node->CallerReference)) 1128 | $dist['callerReference'] = (string) $node->CallerReference; 1129 | if (isset($node->Comment)) 1130 | $dist['comment'] = (string) $node->Comment; 1131 | if (isset($node->Enabled, $node->Origin)) 1132 | { 1133 | $dist['origin'] = (string) $node->Origin; 1134 | $dist['enabled'] = (string) $node->Enabled == 'true' ? true : false; 1135 | } 1136 | elseif (isset($node->DistributionConfig)) 1137 | { 1138 | $dist = array_merge($dist, self::__parseCloudFrontDistributionConfig($node->DistributionConfig)); 1139 | } 1140 | if (isset($node->CNAME)) 1141 | { 1142 | $dist['cnames'] = array(); 1143 | foreach ($node->CNAME as $cname) 1144 | $dist['cnames'][(string) $cname] = (string) $cname; 1145 | } 1146 | return $dist; 1147 | } 1148 | 1149 | /** 1150 | * Grab CloudFront response 1151 | * 1152 | * @internal Used to parse the CloudFront S3Request::getResponse() output 1153 | * @param object &$rest S3Request instance 1154 | * @return object 1155 | */ 1156 | private static function __getCloudFrontResponse(&$rest) 1157 | { 1158 | $rest->getResponse(); 1159 | if ($rest->response->error === false && isset($rest->response->body) && 1160 | is_string($rest->response->body) && substr($rest->response->body, 0, 5) == 'response->body = simplexml_load_string($rest->response->body); 1163 | // Grab CloudFront errors 1164 | if (isset($rest->response->body->Error, $rest->response->body->Error->Code, 1165 | $rest->response->body->Error->Message)) 1166 | { 1167 | $rest->response->error = array( 1168 | 'code' => (string) $rest->response->body->Error->Code, 1169 | 'message' => (string) $rest->response->body->Error->Message 1170 | ); 1171 | unset($rest->response->body); 1172 | } 1173 | } 1174 | return $rest->response; 1175 | } 1176 | 1177 | /** 1178 | * Get MIME type for file 1179 | * 1180 | * @internal Used to get mime types 1181 | * @param string &$file File path 1182 | * @return string 1183 | */ 1184 | public static function __getMimeType(&$file) 1185 | { 1186 | $type = false; 1187 | // Fileinfo documentation says fileinfo_open() will use the 1188 | // MAGIC env var for the magic file 1189 | if (extension_loaded('fileinfo') && isset($_ENV['MAGIC']) && 1190 | ($finfo = finfo_open(FILEINFO_MIME, $_ENV['MAGIC'])) !== false) 1191 | { 1192 | if (($type = finfo_file($finfo, $file)) !== false) 1193 | { 1194 | // Remove the charset and grab the last content-type 1195 | $type = explode(' ', str_replace('; charset=', ';charset=', $type)); 1196 | $type = array_pop($type); 1197 | $type = explode(';', $type); 1198 | $type = trim(array_shift($type)); 1199 | } 1200 | finfo_close($finfo); 1201 | 1202 | // If anyone is still using mime_content_type() 1203 | } 1204 | elseif (function_exists('mime_content_type')) 1205 | $type = trim(mime_content_type($file)); 1206 | 1207 | if ($type !== false && strlen($type) > 0) 1208 | return $type; 1209 | 1210 | // Otherwise do it the old fashioned way 1211 | static $exts = array( 1212 | 'jpg' => 'image/jpeg', 'gif' => 'image/gif', 'png' => 'image/png', 1213 | 'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'ico' => 'image/x-icon', 1214 | 'swf' => 'application/x-shockwave-flash', 'pdf' => 'application/pdf', 1215 | 'zip' => 'application/zip', 'gz' => 'application/x-gzip', 1216 | 'tar' => 'application/x-tar', 'bz' => 'application/x-bzip', 1217 | 'bz2' => 'application/x-bzip2', 'txt' => 'text/plain', 1218 | 'asc' => 'text/plain', 'htm' => 'text/html', 'html' => 'text/html', 1219 | 'css' => 'text/css', 'js' => 'text/javascript', 1220 | 'xml' => 'text/xml', 'xsl' => 'application/xsl+xml', 1221 | 'ogg' => 'application/ogg', 'mp3' => 'audio/mpeg', 'wav' => 'audio/x-wav', 1222 | 'avi' => 'video/x-msvideo', 'mpg' => 'video/mpeg', 'mpeg' => 'video/mpeg', 1223 | 'mov' => 'video/quicktime', 'flv' => 'video/x-flv', 'php' => 'text/x-php' 1224 | ); 1225 | $ext = strtolower(pathInfo($file, PATHINFO_EXTENSION)); 1226 | return isset($exts[$ext]) ? $exts[$ext] : 'application/octet-stream'; 1227 | } 1228 | 1229 | /** 1230 | * Generate the auth string: "AWS AccessKey:Signature" 1231 | * 1232 | * @internal Used by S3Request::getResponse() 1233 | * @param string $string String to sign 1234 | * @return string 1235 | */ 1236 | public static function __getSignature($string) 1237 | { 1238 | return 'AWS ' . self::$__access_key . ':' . self::__getHash($string); 1239 | } 1240 | 1241 | /** 1242 | * Creates a HMAC-SHA1 hash 1243 | * 1244 | * This uses the hash extension if loaded 1245 | * 1246 | * @internal Used by __getSignature() 1247 | * @param string $string String to sign 1248 | * @return string 1249 | */ 1250 | private static function __getHash($string) 1251 | { 1252 | return base64_encode(extension_loaded('hash') ? 1253 | hash_hmac('sha1', $string, self::$__secret_key, true) : pack('H*', sha1( 1254 | (str_pad(self::$__secret_key, 64, chr(0x00)) ^ (str_repeat(chr(0x5c), 64))) . 1255 | pack('H*', sha1((str_pad(self::$__secret_key, 64, chr(0x00)) ^ 1256 | (str_repeat(chr(0x36), 64))) . $string))))); 1257 | } 1258 | } 1259 | 1260 | final class S3Request { 1261 | 1262 | private $verb, $bucket, $uri, $resource = '', $parameters = array(), 1263 | $amzHeaders = array(), $headers = array( 1264 | 'Host' => '', 'Date' => '', 'Content-MD5' => '', 'Content-Type' => '' 1265 | ); 1266 | public $fp = false, $size = 0, $data = false, $response; 1267 | 1268 | /** 1269 | * Constructor 1270 | * 1271 | * @param string $verb Verb 1272 | * @param string $bucket Bucket name 1273 | * @param string $uri Object URI 1274 | * @return mixed 1275 | */ 1276 | function __construct($verb, $bucket = '', $uri = '', $defaultHost = 's3.amazonaws.com') 1277 | { 1278 | $this->verb = $verb; 1279 | $this->bucket = strtolower($bucket); 1280 | $this->uri = $uri !== '' ? '/' . str_replace('%2F', '/', rawurlencode($uri)) : '/'; 1281 | 1282 | if ($this->bucket !== '') 1283 | { 1284 | $this->headers['Host'] = $this->bucket . '.' . $defaultHost; 1285 | $this->resource = '/' . $this->bucket . $this->uri; 1286 | } 1287 | else 1288 | { 1289 | $this->headers['Host'] = $defaultHost; 1290 | //$this->resource = strlen($this->uri) > 1 ? '/'.$this->bucket.$this->uri : $this->uri; 1291 | $this->resource = $this->uri; 1292 | } 1293 | $this->headers['Date'] = gmdate('D, d M Y H:i:s T'); 1294 | 1295 | $this->response = new STDClass; 1296 | $this->response->error = false; 1297 | } 1298 | 1299 | /** 1300 | * Set request parameter 1301 | * 1302 | * @param string $key Key 1303 | * @param string $value Value 1304 | * @return void 1305 | */ 1306 | public function setParameter($key, $value) 1307 | { 1308 | $this->parameters[$key] = $value; 1309 | } 1310 | 1311 | /** 1312 | * Set request header 1313 | * 1314 | * @param string $key Key 1315 | * @param string $value Value 1316 | * @return void 1317 | */ 1318 | public function setHeader($key, $value) 1319 | { 1320 | $this->headers[$key] = $value; 1321 | } 1322 | 1323 | /** 1324 | * Set x-amz-meta-* header 1325 | * 1326 | * @param string $key Key 1327 | * @param string $value Value 1328 | * @return void 1329 | */ 1330 | public function setAmzHeader($key, $value) 1331 | { 1332 | $this->amzHeaders[$key] = $value; 1333 | } 1334 | 1335 | /** 1336 | * Get the S3 response 1337 | * 1338 | * @return object | false 1339 | */ 1340 | public function getResponse() 1341 | { 1342 | $query = ''; 1343 | if (sizeof($this->parameters) > 0) 1344 | { 1345 | $query = substr($this->uri, -1) !== '?' ? '?' : '&'; 1346 | foreach ($this->parameters as $var => $value) 1347 | if ($value == null || $value == '') 1348 | $query .= $var . '&'; 1349 | // Parameters should be encoded (thanks Sean O'Dea) 1350 | else 1351 | $query .= $var . '=' . rawurlencode($value) . '&'; 1352 | $query = substr($query, 0, -1); 1353 | $this->uri .= $query; 1354 | 1355 | if (array_key_exists('acl', $this->parameters) || 1356 | array_key_exists('location', $this->parameters) || 1357 | array_key_exists('torrent', $this->parameters) || 1358 | array_key_exists('logging', $this->parameters)) 1359 | $this->resource .= $query; 1360 | } 1361 | $url = ((S3::$use_ssl && extension_loaded('openssl')) ? 1362 | 'https://' : 'http://') . $this->headers['Host'] . $this->uri; 1363 | //var_dump($this->bucket, $this->uri, $this->resource, $url); 1364 | // Basic setup 1365 | $curl = curl_init(); 1366 | curl_setopt($curl, CURLOPT_USERAGENT, 'S3/php'); 1367 | 1368 | if (S3::$use_ssl) 1369 | { 1370 | curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2); 1371 | if (S3::$verify_peer) 1372 | { 1373 | curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 1); 1374 | } 1375 | else 1376 | { 1377 | curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE); 1378 | } 1379 | } 1380 | 1381 | curl_setopt($curl, CURLOPT_URL, $url); 1382 | 1383 | // Headers 1384 | $headers = array(); 1385 | $amz = array(); 1386 | foreach ($this->amzHeaders as $header => $value) 1387 | if (strlen($value) > 0) 1388 | $headers[] = $header . ': ' . $value; 1389 | foreach ($this->headers as $header => $value) 1390 | if (strlen($value) > 0) 1391 | $headers[] = $header . ': ' . $value; 1392 | 1393 | // Collect AMZ headers for signature 1394 | foreach ($this->amzHeaders as $header => $value) 1395 | if (strlen($value) > 0) 1396 | $amz[] = strtolower($header) . ':' . $value; 1397 | 1398 | // AMZ headers must be sorted 1399 | if (sizeof($amz) > 0) 1400 | { 1401 | sort($amz); 1402 | $amz = "\n" . implode("\n", $amz); 1403 | } else 1404 | $amz = ''; 1405 | 1406 | // Authorization string (CloudFront stringToSign should only contain a date) 1407 | $headers[] = 'Authorization: ' . S3::__getSignature( 1408 | $this->headers['Host'] == 'cloudfront.amazonaws.com' ? $this->headers['Date'] : 1409 | $this->verb . "\n" . $this->headers['Content-MD5'] . "\n" . 1410 | $this->headers['Content-Type'] . "\n" . $this->headers['Date'] . $amz . "\n" . $this->resource 1411 | ); 1412 | 1413 | curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); 1414 | curl_setopt($curl, CURLOPT_HEADER, false); 1415 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, false); 1416 | curl_setopt($curl, CURLOPT_WRITEFUNCTION, array(&$this, '__responseWriteCallback')); 1417 | curl_setopt($curl, CURLOPT_HEADERFUNCTION, array(&$this, '__responseHeaderCallback')); 1418 | curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); 1419 | 1420 | // Request types 1421 | switch ($this->verb) 1422 | { 1423 | case 'GET': break; 1424 | case 'PUT': case 'POST': // POST only used for CloudFront 1425 | if ($this->fp !== false) 1426 | { 1427 | curl_setopt($curl, CURLOPT_PUT, true); 1428 | curl_setopt($curl, CURLOPT_INFILE, $this->fp); 1429 | if ($this->size >= 0) 1430 | curl_setopt($curl, CURLOPT_INFILESIZE, $this->size); 1431 | } elseif ($this->data !== false) 1432 | { 1433 | curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb); 1434 | curl_setopt($curl, CURLOPT_POSTFIELDS, $this->data); 1435 | if ($this->size >= 0) 1436 | curl_setopt($curl, CURLOPT_BUFFERSIZE, $this->size); 1437 | } else 1438 | curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb); 1439 | break; 1440 | case 'HEAD': 1441 | curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'HEAD'); 1442 | curl_setopt($curl, CURLOPT_NOBODY, true); 1443 | break; 1444 | case 'DELETE': 1445 | curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE'); 1446 | break; 1447 | default: break; 1448 | } 1449 | 1450 | // Execute, grab errors 1451 | if (curl_exec($curl)) 1452 | $this->response->code = curl_getinfo($curl, CURLINFO_HTTP_CODE); 1453 | else 1454 | $this->response->error = array( 1455 | 'code' => curl_errno($curl), 1456 | 'message' => curl_error($curl), 1457 | 'resource' => $this->resource 1458 | ); 1459 | 1460 | @curl_close($curl); 1461 | 1462 | // Parse body 1463 | if ($this->response->error === false && isset($this->response->headers['type']) && isset($this->response->body)) 1464 | { 1465 | if($this->response->headers['type'] == 'application/json') 1466 | { 1467 | $this->response->body = json_decode($this->response->body); 1468 | }elseif($this->response->headers['type']== 'application/xml') 1469 | { 1470 | $this->response->body = simplexml_load_string($this->response->body); 1471 | } 1472 | // Grab S3 errors 1473 | if (!in_array($this->response->code, array(200, 204)) && 1474 | isset($this->response->body->Code, $this->response->body->Message)) 1475 | { 1476 | $this->response->error = array( 1477 | 'code' => (string) $this->response->body->Code, 1478 | 'message' => (string) $this->response->body->Message 1479 | ); 1480 | if (isset($this->response->body->Resource)) 1481 | $this->response->error['resource'] = (string) $this->response->body->Resource; 1482 | unset($this->response->body); 1483 | } 1484 | } 1485 | 1486 | // Clean up file resources 1487 | if ($this->fp !== false && is_resource($this->fp)) 1488 | fclose($this->fp); 1489 | 1490 | return $this->response; 1491 | } 1492 | 1493 | /** 1494 | * CURL write callback 1495 | * 1496 | * @param resource &$curl CURL resource 1497 | * @param string &$data Data 1498 | * @return integer 1499 | */ 1500 | private function __responseWriteCallback(&$curl, &$data) 1501 | { 1502 | if ($this->response->code == 200 && $this->fp !== false) 1503 | return fwrite($this->fp, $data); 1504 | else 1505 | if (isset($this->response->body)) 1506 | $this->response->body .= $data; 1507 | else 1508 | $this->response->body = $data; 1509 | return strlen($data); 1510 | } 1511 | 1512 | /** 1513 | * CURL header callback 1514 | * 1515 | * @param resource &$curl CURL resource 1516 | * @param string &$data Data 1517 | * @return integer 1518 | */ 1519 | private function __responseHeaderCallback(&$curl, &$data) 1520 | { 1521 | if (($strlen = strlen($data)) <= 2) 1522 | return $strlen; 1523 | if (substr($data, 0, 4) == 'HTTP') 1524 | $this->response->code = (int) substr($data, 9, 3); 1525 | else 1526 | { 1527 | list($header, $value) = explode(': ', trim($data), 2); 1528 | if ($header == 'Last-Modified') 1529 | $this->response->headers['time'] = strtotime($value); 1530 | elseif ($header == 'Content-Length') 1531 | $this->response->headers['size'] = (int) $value; 1532 | elseif ($header == 'Content-Type') 1533 | $this->response->headers['type'] = $value; 1534 | elseif ($header == 'ETag') 1535 | $this->response->headers['hash'] = $value{0} == '"' ? substr($value, 1, -1) : $value; 1536 | elseif (preg_match('/^x-amz-meta-.*$/', $header)) 1537 | $this->response->headers[$header] = is_numeric($value) ? (int) $value : $value; 1538 | } 1539 | return $strlen; 1540 | } 1541 | 1542 | } 1543 | --------------------------------------------------------------------------------