PluginProbe
ManageWP Worker / 3.9.8
ManageWP Worker v3.9.8
4.9.38 4.9.37 4.9.36 4.9.35 4.9.34 3.8.7 3.8.8 3.9.0 3.9.1 3.9.10 3.9.11 3.9.12 3.9.13 3.9.14 3.9.15 3.9.16 3.9.17 3.9.18 3.9.19 3.9.2 3.9.20 3.9.21 3.9.22 3.9.23 3.9.24 All 73 releases
worker / lib / s3.php

s3.php in ManageWP Worker 3.9.8, at lib/s3.php

1,923 lines 62.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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:
9 *
10 * - Redistributions of source code must retain the above copyright notice,
11 * this list of conditions and the following disclaimer.
12 * - Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 *
28 * Amazon S3 is a trademark of Amazon.com, Inc. or its affiliates.
29 */
30
31 /**
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 /**
69 * Constructor - if you're not using the class statically
70 *
71 * @param string $accessKey Access key
72 * @param string $secretKey Secret key
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 *
99 * @param string $accessKey Access key
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
252 );
253 $results['buckets'] = array();
254 foreach ($rest->body->Buckets->Bucket as $b)
255 $results['buckets'][] = array(
256 'name' => (string)$b->Name, 'time' => strtotime((string)$b->CreationDate)
257 );
258 } else
259 foreach ($rest->body->Buckets->Bucket as $b) $results[] = (string)$b->Name;
260
261 return $results;
262 }
263
264
265 /*
266 * Get contents for a bucket
267 *
268 * If maxKeys is null this method will loop through truncated result sets
269 *
270 * @param string $bucket Bucket name
271 * @param string $prefix Prefix
272 * @param string $marker Marker (last file listed)
273 * @param string $maxKeys Max keys (maximum number of keys to return)
274 * @param string $delimiter Delimiter
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);
285 if ($delimiter !== null && $delimiter !== '') $rest->setParameter('delimiter', $delimiter);
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
296 $results = array();
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),
305 'size' => (int)$c->Size,
306 'hash' => substr((string)$c->ETag, 1, -1)
307 );
308 $nextMarker = (string)$c->Key;
309 }
310
311 if ($returnCommonPrefixes && isset($response->body, $response->body->CommonPrefixes))
312 foreach ($response->body->CommonPrefixes as $c)
313 $results[(string)$c->Prefix] = array('prefix' => (string)$c->Prefix);
314
315 if (isset($response->body, $response->body->IsTruncated) &&
316 (string)$response->body->IsTruncated == 'false') return $results;
317
318 if (isset($response->body, $response->body->NextMarker))
319 $nextMarker = (string)$response->body->NextMarker;
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(true)) == 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),
338 'size' => (int)$c->Size,
339 'hash' => substr((string)$c->ETag, 1, -1)
340 );
341 $nextMarker = (string)$c->Key;
342 }
343
344 if ($returnCommonPrefixes && isset($response->body, $response->body->CommonPrefixes))
345 foreach ($response->body->CommonPrefixes as $c)
346 $results[(string)$c->Prefix] = array('prefix' => (string)$c->Prefix);
347
348 if (isset($response->body, $response->body->NextMarker))
349 $nextMarker = (string)$response->body->NextMarker;
350
351 } while ($response !== false && (string)$response->body->IsTruncated == 'true');
352
353 return $results;
354 }
355
356
357 /**
358 * Put a bucket
359 *
360 * @param string $bucket Bucket name
361 * @param constant $acl ACL flag
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', strtoupper($location));
375 $createBucketConfiguration->appendChild($locationConstraint);
376 $dom->appendChild($createBucketConfiguration);
377 $rest->data = $dom->saveXML();
378 $rest->size = strlen($rest->data);
379 $rest->setHeader('Content-Type', 'application/xml');
380 }
381 $rest = $rest->getResponse();
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;
392 }
393
394
395 /**
396 * Delete an empty bucket
397 *
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;
414 }
415
416
417 /**
418 * Create input info array for putObject()
419 *
420 * @param string $file Input file
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
436 /**
437 * Create input array info for putObject() with a resource
438 *
439 * @param string $resource Input resource to read from
440 * @param integer $bufferSize Input byte size
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);
452 $input['fp'] =& $resource;
453 return $input;
454 }
455
456
457 /**
458 * Put an object
459 *
460 * @param mixed $input Input data
461 * @param string $bucket Bucket name
462 * @param string $uri Object URI
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_string($input)) $input = array(
475 'data' => $input, 'size' => strlen($input),
476 'md5sum' => base64_encode(md5($input, true))
477 );
478
479 // Data
480 if (isset($input['fp']))
481 $rest->fp =& $input['fp'];
482 elseif (isset($input['file']))
483 $rest->fp = @fopen($input['file'], 'rb');
484 elseif (isset($input['data']))
485 $rest->data = $input['data'];
486
487 // Content-Length (required)
488 if (isset($input['size']) && $input['size'] >= 0)
489 $rest->size = $input['size'];
490 else {
491 if (isset($input['file']))
492 $rest->size = filesize($input['file']);
493 elseif (isset($input['data']))
494 $rest->size = strlen($input['data']);
495 }
496
497 // Custom request headers (Content-Type, Content-Disposition, Content-Encoding)
498 if (is_array($requestHeaders))
499 foreach ($requestHeaders as $h => $v) $rest->setHeader($h, $v);
500 elseif (is_string($requestHeaders)) // Support for legacy contentType parameter
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']))
509 $input['type'] = self::__getMimeType($input['file']);
510 else
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
523 $rest->setAmzHeader('x-amz-acl', $acl);
524 foreach ($metaHeaders as $h => $v) $rest->setAmzHeader('x-amz-meta-'.$h, $v);
525 $rest->getResponse();
526 } else
527 $rest->response->error = array('code' => 0, 'message' => 'Missing input parameters');
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;
538 }
539
540
541 /**
542 * Put an object from a file (legacy function)
543 *
544 * @param string $file Input file path
545 * @param string $bucket Bucket name
546 * @param string $uri Object URI
547 * @param constant $acl ACL constant
548 * @param array $metaHeaders Array of x-amz-meta-* headers
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
557
558 /**
559 * Put an object from a string (legacy function)
560 *
561 * @param string $string Input data
562 * @param string $bucket Bucket name
563 * @param string $uri Object URI
564 * @param constant $acl ACL constant
565 * @param array $metaHeaders Array of x-amz-meta-* headers
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
574
575 /**
576 * Get an object
577 *
578 * @param string $bucket Bucket name
579 * @param string $uri Object URI
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
591 if (($rest->fp = @fopen($saveTo, 'wb')) !== false)
592 $rest->file = realpath($saveTo);
593 else
594 $rest->response->error = array('code' => 0, 'message' => 'Unable to open save file for writing: '.$saveTo);
595 }
596 if ($rest->response->error === false) $rest->getResponse();
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;
607 }
608
609
610 /**
611 * Get object information
612 *
613 * @param string $bucket Bucket name
614 * @param string $uri Object URI
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;
631 }
632
633
634 /**
635 * Copy an object
636 *
637 * @param string $bucket Source bucket name
638 * @param string $uri Source object URI
639 * @param string $bucket Destination bucket name
640 * @param string $uri Destination object URI
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); // Added rawurlencode() for $srcUri (thanks a.yamanoi)
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(
670 'time' => strtotime((string)$rest->body->LastModified),
671 'hash' => substr((string)$rest->body->ETag, 1, -1)
672 ) : false;
673 }
674
675
676 /**
677 * Set logging for a bucket
678 *
679 * @param string $bucket Bucket name
680 * @param string $targetBucket Target bucket (where logs are stored)
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 }
698 if (!$aclWriteSet) $acp['acl'][] = array(
699 'type' => 'Group', 'uri' => 'http://acs.amazonaws.com/groups/s3/LogDelivery', 'permission' => 'WRITE'
700 );
701 if (!$aclReadSet) $acp['acl'][] = array(
702 'type' => 'Group', 'uri' => 'http://acs.amazonaws.com/groups/s3/LogDelivery', 'permission' => 'READ_ACP'
703 );
704 if (!$aclReadSet || !$aclWriteSet) self::setAccessControlPolicy($targetBucket, '', $acp);
705 }
706
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));
715 $loggingEnabled->appendChild($dom->createElement('TargetPrefix', $targetPrefix));
716 // TODO: Add TargetGrants?
717 $bucketLoggingStatus->appendChild($loggingEnabled);
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);
725 $rest->setHeader('Content-Type', 'application/xml');
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}, {$uri}): [%s] %s",
732 $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
733 return false;
734 }
735 return true;
736 }
737
738
739 /**
740 * Get logging status for a bucket
741 *
742 * This will return false if logging is not enabled.
743 * Note: To enable logging, you also need to grant write access to the log group
744 *
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
762 return array(
763 'targetBucket' => (string)$rest->body->LoggingEnabled->TargetBucket,
764 'targetPrefix' => (string)$rest->body->LoggingEnabled->TargetPrefix,
765 );
766 }
767
768
769 /**
770 * Disable bucket logging
771 *
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
780
781 /**
782 * Get a bucket's location
783 *
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';
801 }
802
803
804 /**
805 * Set object or bucket Access Control Policy
806 *
807 * @param string $bucket Bucket name
808 * @param string $uri Object URI
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');
817 $accessControlList = $dom->createElement('AccessControlList');
818
819 // It seems the owner has to be passed along too
820 $owner = $dom->createElement('Owner');
821 $owner->appendChild($dom->createElement('ID', $acp['owner']['id']));
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 }
845 $grant->appendChild($grantee);
846 $grant->appendChild($dom->createElement('Permission', $g['permission']));
847 $accessControlList->appendChild($grant);
848 }
849
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);
857 $rest->setHeader('Content-Type', 'application/xml');
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;
868 }
869
870
871 /**
872 * Get object or bucket Access Control Policy
873 *
874 * @param string $bucket Bucket name
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',
908 'id' => (string)$grantee->ID,
909 'name' => (string)$grantee->DisplayName,
910 'permission' => (string)$grant->Permission
911 );
912 elseif (isset($grantee->EmailAddress)) // AmazonCustomerByEmail
913 $acp['acl'][] = array(
914 'type' => 'AmazonCustomerByEmail',
915 'email' => (string)$grantee->EmailAddress,
916 'permission' => (string)$grant->Permission
917 );
918 elseif (isset($grantee->URI)) // Group
919 $acp['acl'][] = array(
920 'type' => 'Group',
921 'uri' => (string)$grantee->URI,
922 'permission' => (string)$grant->Permission
923 );
924 else continue;
925 }
926 }
927 }
928 return $acp;
929 }
930
931
932 /**
933 * Delete an object
934 *
935 * @param string $bucket Bucket name
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;
952 }
953
954
955 /**
956 * Get a query string authenticated URL
957 *
958 * @param string $bucket Bucket name
959 * @param string $uri Object URI
960 * @param integer $lifetime Lifetime in seconds
961 * @param boolean $hostBucket Use the bucket name as the hostname
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('%2F', '/', rawurlencode($uri)); // URI should be encoded (thanks Sean O'Dea)
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 urlencode(self::__getHash("GET\n\n\n{$expires}\n/{$bucket}/{$uri}")));
972 }
973
974
975 /**
976 * Get a CloudFront signed policy URL
977 *
978 * @param array $policy Policy
979 * @return string
980 */
981 public static function getSignedPolicyURL($policy)
982 {
983 $data = json_encode($policy);
984 $signature = '';
985 if (!openssl_sign($data, $signature, self::$__signingKeyResource)) return false;
986
987 $encoded = str_replace(array('+', '='), array('-', '_', '~'), base64_encode($data));
988 $signature = str_replace(array('+', '='), array('-', '_', '~'), base64_encode($signature));
989
990 $url = $policy['Statement'][0]['Resource'] . '?';
991
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 *
1020 * @param string $bucket Bucket name
1021 * @param string $uriPrefix Object URI prefix
1022 * @param constant $acl ACL constant
1023 * @param integer $lifetime Lifetime in seconds
1024 * @param integer $maxFileSize Maximum filesize in bytes (default 5MB)
1025 * @param string $successRedirect Redirect URL or 200 / 201 status code
1026 * @param array $amzHeaders Array of x-amz-meta-* headers
1027 * @param array $headers Array of request headers or content type as a string
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));
1037 $policy->conditions = array();
1038 $obj = new stdClass; $obj->bucket = $bucket; array_push($policy->conditions, $obj);
1039 $obj = new stdClass; $obj->acl = $acl; array_push($policy->conditions, $obj);
1040
1041 $obj = new stdClass; // 200 for non-redirect uploads
1042 if (is_numeric($successRedirect) && in_array((int)$successRedirect, array(200, 201)))
1043 $obj->success_action_status = (string)$successRedirect;
1044 else // URL
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;
1067 $params->key = $uriPrefix.'${filename}';
1068 $params->acl = $acl;
1069 $params->policy = $policy; unset($policy);
1070 $params->signature = self::__getHash($params->policy);
1071 if (is_numeric($successRedirect) && in_array((int)$successRedirect, array(200, 201)))
1072 $params->success_action_status = (string)$successRedirect;
1073 else
1074 $params->success_action_redirect = $successRedirect;
1075 foreach ($headers as $headerKey => $headerVal) $params->{$headerKey} = (string)$headerVal;
1076 foreach ($amzHeaders as $headerKey => $headerVal) $params->{$headerKey} = (string)$headerVal;
1077 return $params;
1078 }
1079
1080
1081 /**
1082 * Create a CloudFront distribution
1083 *
1084 * @param string $bucket Bucket name
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);
1131 return false;
1132 }
1133
1134
1135 /**
1136 * Get CloudFront distribution info
1137 *
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;
1173 }
1174
1175
1176 /**
1177 * Update a CloudFront distribution
1178 *
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);
1221 $dist['hash'] = $rest->headers['hash'];
1222 return $dist;
1223 }
1224 return false;
1225 }
1226
1227
1228 /**
1229 * Delete a CloudFront distribution
1230 *
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;
1261 }
1262
1263
1264 /**
1265 * Get a list of CloudFront distributions
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 * Get a DistributionConfig DOMDocument
1409 *
1410 * http://docs.amazonwebservices.com/AmazonCloudFront/latest/APIReference/index.html?PutConfig.html
1411 *
1412 * @internal Used to create XML in createDistribution() and updateDistribution()
1413 * @param string $bucket S3 Origin bucket
1414 * @param boolean $enabled Enabled (true/false)
1415 * @param string $comment Comment to append
1416 * @param string $callerReference Caller reference
1417 * @param array $cnames Array of CNAME aliases
1418 * @param string $defaultRootObject Default root object
1419 * @param string $originAccessIdentity Origin access identity
1420 * @param array $trustedSigners Array of trusted signers
1421 * @return string
1422 */
1423 private static function __getCloudFrontDistributionConfigXML($bucket, $enabled, $comment, $callerReference = '0', $cnames = array(), $defaultRootObject = null, $originAccessIdentity = null, $trustedSigners = array())
1424 {
1425 $dom = new DOMDocument('1.0', 'UTF-8');
1426 $dom->formatOutput = true;
1427 $distributionConfig = $dom->createElement('DistributionConfig');
1428 $distributionConfig->setAttribute('xmlns', 'http://cloudfront.amazonaws.com/doc/2010-11-01/');
1429
1430 $origin = $dom->createElement('S3Origin');
1431 $origin->appendChild($dom->createElement('DNSName', $bucket));
1432 if ($originAccessIdentity !== null) $origin->appendChild($dom->createElement('OriginAccessIdentity', $originAccessIdentity));
1433 $distributionConfig->appendChild($origin);
1434
1435 if ($defaultRootObject !== null) $distributionConfig->appendChild($dom->createElement('DefaultRootObject', $defaultRootObject));
1436
1437 $distributionConfig->appendChild($dom->createElement('CallerReference', $callerReference));
1438 foreach ($cnames as $cname)
1439 $distributionConfig->appendChild($dom->createElement('CNAME', $cname));
1440 if ($comment !== '') $distributionConfig->appendChild($dom->createElement('Comment', $comment));
1441 $distributionConfig->appendChild($dom->createElement('Enabled', $enabled ? 'true' : 'false'));
1442
1443 $trusted = $dom->createElement('TrustedSigners');
1444 foreach ($trustedSigners as $id => $type)
1445 $trusted->appendChild($id !== '' ? $dom->createElement($type, $id) : $dom->createElement($type));
1446 $distributionConfig->appendChild($trusted);
1447
1448 $dom->appendChild($distributionConfig);
1449 //var_dump($dom->saveXML());
1450 return $dom->saveXML();
1451 }
1452
1453
1454 /**
1455 * Parse a CloudFront distribution config
1456 *
1457 * See http://docs.amazonwebservices.com/AmazonCloudFront/latest/APIReference/index.html?GetDistribution.html
1458 *
1459 * @internal Used to parse the CloudFront DistributionConfig node to an array
1460 * @param object &$node DOMNode
1461 * @return array
1462 */
1463 private static function __parseCloudFrontDistributionConfig(&$node)
1464 {
1465 if (isset($node->DistributionConfig))
1466 return self::__parseCloudFrontDistributionConfig($node->DistributionConfig);
1467
1468 $dist = array();
1469 if (isset($node->Id, $node->Status, $node->LastModifiedTime, $node->DomainName))
1470 {
1471 $dist['id'] = (string)$node->Id;
1472 $dist['status'] = (string)$node->Status;
1473 $dist['time'] = strtotime((string)$node->LastModifiedTime);
1474 $dist['domain'] = (string)$node->DomainName;
1475 }
1476
1477 if (isset($node->CallerReference))
1478 $dist['callerReference'] = (string)$node->CallerReference;
1479
1480 if (isset($node->Enabled))
1481 $dist['enabled'] = (string)$node->Enabled == 'true' ? true : false;
1482
1483 if (isset($node->S3Origin))
1484 {
1485 if (isset($node->S3Origin->DNSName))
1486 $dist['origin'] = (string)$node->S3Origin->DNSName;
1487
1488 $dist['originAccessIdentity'] = isset($node->S3Origin->OriginAccessIdentity) ?
1489 (string)$node->S3Origin->OriginAccessIdentity : null;
1490 }
1491
1492 $dist['defaultRootObject'] = isset($node->DefaultRootObject) ? (string)$node->DefaultRootObject : null;
1493
1494 $dist['cnames'] = array();
1495 if (isset($node->CNAME))
1496 foreach ($node->CNAME as $cname)
1497 $dist['cnames'][(string)$cname] = (string)$cname;
1498
1499 $dist['trustedSigners'] = array();
1500 if (isset($node->TrustedSigners))
1501 foreach ($node->TrustedSigners as $signer)
1502 {
1503 if (isset($signer->Self))
1504 $dist['trustedSigners'][''] = 'Self';
1505 elseif (isset($signer->KeyPairId))
1506 $dist['trustedSigners'][(string)$signer->KeyPairId] = 'KeyPairId';
1507 elseif (isset($signer->AwsAccountNumber))
1508 $dist['trustedSigners'][(string)$signer->AwsAccountNumber] = 'AwsAccountNumber';
1509 }
1510
1511 $dist['comment'] = isset($node->Comment) ? (string)$node->Comment : null;
1512 return $dist;
1513 }
1514
1515
1516 /**
1517 * Grab CloudFront response
1518 *
1519 * @internal Used to parse the CloudFront S3Request::getResponse() output
1520 * @param object &$rest S3Request instance
1521 * @return object
1522 */
1523 private static function __getCloudFrontResponse(&$rest)
1524 {
1525 $rest->getResponse();
1526 if ($rest->response->error === false && isset($rest->response->body) &&
1527 is_string($rest->response->body) && substr($rest->response->body, 0, 5) == '<?xml')
1528 {
1529 $rest->response->body = simplexml_load_string($rest->response->body);
1530 // Grab CloudFront errors
1531 if (isset($rest->response->body->Error, $rest->response->body->Error->Code,
1532 $rest->response->body->Error->Message))
1533 {
1534 $rest->response->error = array(
1535 'code' => (string)$rest->response->body->Error->Code,
1536 'message' => (string)$rest->response->body->Error->Message
1537 );
1538 unset($rest->response->body);
1539 }
1540 }
1541 return $rest->response;
1542 }
1543
1544
1545 /**
1546 * Get MIME type for file
1547 *
1548 * @internal Used to get mime types
1549 * @param string &$file File path
1550 * @return string
1551 */
1552 public static function __getMimeType(&$file)
1553 {
1554 $type = false;
1555 // Fileinfo documentation says fileinfo_open() will use the
1556 // MAGIC env var for the magic file
1557 if (extension_loaded('fileinfo') && isset($_ENV['MAGIC']) &&
1558 ($finfo = finfo_open(FILEINFO_MIME, $_ENV['MAGIC'])) !== false)
1559 {
1560 if (($type = finfo_file($finfo, $file)) !== false)
1561 {
1562 // Remove the charset and grab the last content-type
1563 $type = explode(' ', str_replace('; charset=', ';charset=', $type));
1564 $type = array_pop($type);
1565 $type = explode(';', $type);
1566 $type = trim(array_shift($type));
1567 }
1568 finfo_close($finfo);
1569
1570 // If anyone is still using mime_content_type()
1571 } elseif (function_exists('mime_content_type'))
1572 $type = trim(mime_content_type($file));
1573
1574 if ($type !== false && strlen($type) > 0) return $type;
1575
1576 // Otherwise do it the old fashioned way
1577 static $exts = array(
1578 'jpg' => 'image/jpeg', 'gif' => 'image/gif', 'png' => 'image/png',
1579 'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'ico' => 'image/x-icon',
1580 'swf' => 'application/x-shockwave-flash', 'pdf' => 'application/pdf',
1581 'zip' => 'application/zip', 'gz' => 'application/x-gzip',
1582 'tar' => 'application/x-tar', 'bz' => 'application/x-bzip',
1583 'bz2' => 'application/x-bzip2', 'txt' => 'text/plain',
1584 'asc' => 'text/plain', 'htm' => 'text/html', 'html' => 'text/html',
1585 'css' => 'text/css', 'js' => 'text/javascript',
1586 'xml' => 'text/xml', 'xsl' => 'application/xsl+xml',
1587 'ogg' => 'application/ogg', 'mp3' => 'audio/mpeg', 'wav' => 'audio/x-wav',
1588 'avi' => 'video/x-msvideo', 'mpg' => 'video/mpeg', 'mpeg' => 'video/mpeg',
1589 'mov' => 'video/quicktime', 'flv' => 'video/x-flv', 'php' => 'text/x-php'
1590 );
1591 $ext = strtolower(pathInfo($file, PATHINFO_EXTENSION));
1592 return isset($exts[$ext]) ? $exts[$ext] : 'application/octet-stream';
1593 }
1594
1595
1596 /**
1597 * Generate the auth string: "AWS AccessKey:Signature"
1598 *
1599 * @internal Used by S3Request::getResponse()
1600 * @param string $string String to sign
1601 * @return string
1602 */
1603 public static function __getSignature($string)
1604 {
1605 return 'AWS '.self::$__accessKey.':'.self::__getHash($string);
1606 }
1607
1608
1609 /**
1610 * Creates a HMAC-SHA1 hash
1611 *
1612 * This uses the hash extension if loaded
1613 *
1614 * @internal Used by __getSignature()
1615 * @param string $string String to sign
1616 * @return string
1617 */
1618 private static function __getHash($string)
1619 {
1620 return base64_encode(extension_loaded('hash') ?
1621 hash_hmac('sha1', $string, self::$__secretKey, true) : pack('H*', sha1(
1622 (str_pad(self::$__secretKey, 64, chr(0x00)) ^ (str_repeat(chr(0x5c), 64))) .
1623 pack('H*', sha1((str_pad(self::$__secretKey, 64, chr(0x00)) ^
1624 (str_repeat(chr(0x36), 64))) . $string)))));
1625 }
1626
1627 }
1628
1629 final class S3Request
1630 {
1631 private $endpoint, $verb, $bucket, $uri, $resource = '', $parameters = array(),
1632 $amzHeaders = array(), $headers = array(
1633 'Host' => '', 'Date' => '', 'Content-MD5' => '', 'Content-Type' => ''
1634 );
1635 public $fp = false, $size = 0, $data = false, $response;
1636
1637
1638 /**
1639 * Constructor
1640 *
1641 * @param string $verb Verb
1642 * @param string $bucket Bucket name
1643 * @param string $uri Object URI
1644 * @return mixed
1645 */
1646 function __construct($verb, $bucket = '', $uri = '', $endpoint = 's3.amazonaws.com')
1647 {
1648 $this->endpoint = $endpoint;
1649 $this->verb = $verb;
1650 $this->bucket = $bucket;
1651 $this->uri = $uri !== '' ? '/'.str_replace('%2F', '/', rawurlencode($uri)) : '/';
1652
1653 if ($this->bucket !== '')
1654 {
1655 $this->headers['Host'] = $this->bucket.'.'.$this->endpoint;
1656 $this->resource = '/'.$this->bucket.$this->uri;
1657 }
1658 else
1659 {
1660 $this->headers['Host'] = $this->endpoint;
1661 $this->resource = $this->uri;
1662 }
1663 $this->headers['Date'] = gmdate('D, d M Y H:i:s T');
1664
1665 $this->response = new STDClass;
1666 $this->response->error = false;
1667 }
1668
1669
1670 /**
1671 * Set request parameter
1672 *
1673 * @param string $key Key
1674 * @param string $value Value
1675 * @return void
1676 */
1677 public function setParameter($key, $value)
1678 {
1679 $this->parameters[$key] = $value;
1680 }
1681
1682
1683 /**
1684 * Set request header
1685 *
1686 * @param string $key Key
1687 * @param string $value Value
1688 * @return void
1689 */
1690 public function setHeader($key, $value)
1691 {
1692 $this->headers[$key] = $value;
1693 }
1694
1695
1696 /**
1697 * Set x-amz-meta-* header
1698 *
1699 * @param string $key Key
1700 * @param string $value Value
1701 * @return void
1702 */
1703 public function setAmzHeader($key, $value)
1704 {
1705 $this->amzHeaders[$key] = $value;
1706 }
1707
1708
1709 /**
1710 * Get the S3 response
1711 *
1712 * @return object | false
1713 */
1714 public function getResponse()
1715 {
1716 $query = '';
1717 if (sizeof($this->parameters) > 0)
1718 {
1719 $query = substr($this->uri, -1) !== '?' ? '?' : '&';
1720 foreach ($this->parameters as $var => $value)
1721 if ($value == null || $value == '') $query .= $var.'&';
1722 // Parameters should be encoded (thanks Sean O'Dea)
1723 else $query .= $var.'='.rawurlencode($value).'&';
1724 $query = substr($query, 0, -1);
1725 $this->uri .= $query;
1726
1727 if (array_key_exists('acl', $this->parameters) ||
1728 array_key_exists('location', $this->parameters) ||
1729 array_key_exists('torrent', $this->parameters) ||
1730 array_key_exists('logging', $this->parameters))
1731 $this->resource .= $query;
1732 }
1733 $url = (S3::$useSSL ? 'https://' : 'http://') . $this->headers['Host'].$this->uri;
1734 //var_dump($this->bucket, $this->uri, $this->resource, $url);
1735
1736 // Basic setup
1737 $curl = curl_init();
1738 curl_setopt($curl, CURLOPT_USERAGENT, 'S3/php');
1739
1740 if (S3::$useSSL)
1741 {
1742 // SSL Validation can now be optional for those with broken OpenSSL installations
1743 curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, S3::$useSSLValidation ? 1 : 0);
1744 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, S3::$useSSLValidation ? 1 : 0);
1745
1746 if (S3::$sslKey !== null) curl_setopt($curl, CURLOPT_SSLKEY, S3::$sslKey);
1747 if (S3::$sslCert !== null) curl_setopt($curl, CURLOPT_SSLCERT, S3::$sslCert);
1748 if (S3::$sslCACert !== null) curl_setopt($curl, CURLOPT_CAINFO, S3::$sslCACert);
1749 }
1750
1751 curl_setopt($curl, CURLOPT_URL, $url);
1752
1753 if (S3::$proxy != null && isset(S3::$proxy['host']))
1754 {
1755 curl_setopt($curl, CURLOPT_PROXY, S3::$proxy['host']);
1756 curl_setopt($curl, CURLOPT_PROXYTYPE, S3::$proxy['type']);
1757 if (isset(S3::$proxy['user'], S3::$proxy['pass']) && $proxy['user'] != null && $proxy['pass'] != null)
1758 curl_setopt($curl, CURLOPT_PROXYUSERPWD, sprintf('%s:%s', S3::$proxy['user'], S3::$proxy['pass']));
1759 }
1760
1761 // Headers
1762 $headers = array(); $amz = array();
1763 foreach ($this->amzHeaders as $header => $value)
1764 if (strlen($value) > 0) $headers[] = $header.': '.$value;
1765 foreach ($this->headers as $header => $value)
1766 if (strlen($value) > 0) $headers[] = $header.': '.$value;
1767
1768 // Collect AMZ headers for signature
1769 foreach ($this->amzHeaders as $header => $value)
1770 if (strlen($value) > 0) $amz[] = strtolower($header).':'.$value;
1771
1772 // AMZ headers must be sorted
1773 if (sizeof($amz) > 0)
1774 {
1775 sort($amz);
1776 $amz = "\n".implode("\n", $amz);
1777 } else $amz = '';
1778
1779 if (S3::hasAuth())
1780 {
1781 // Authorization string (CloudFront stringToSign should only contain a date)
1782 $headers[] = 'Authorization: ' . S3::__getSignature(
1783 $this->headers['Host'] == 'cloudfront.amazonaws.com' ? $this->headers['Date'] :
1784 $this->verb."\n".$this->headers['Content-MD5']."\n".
1785 $this->headers['Content-Type']."\n".$this->headers['Date'].$amz."\n".$this->resource
1786 );
1787 }
1788
1789 curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
1790 curl_setopt($curl, CURLOPT_HEADER, false);
1791 curl_setopt($curl, CURLOPT_RETURNTRANSFER, false);
1792 curl_setopt($curl, CURLOPT_WRITEFUNCTION, array(&$this, '__responseWriteCallback'));
1793 curl_setopt($curl, CURLOPT_HEADERFUNCTION, array(&$this, '__responseHeaderCallback'));
1794 curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
1795
1796 // Request types
1797 switch ($this->verb)
1798 {
1799 case 'GET': break;
1800 case 'PUT': case 'POST': // POST only used for CloudFront
1801 if ($this->fp !== false)
1802 {
1803 curl_setopt($curl, CURLOPT_PUT, true);
1804 curl_setopt($curl, CURLOPT_INFILE, $this->fp);
1805 if ($this->size >= 0)
1806 curl_setopt($curl, CURLOPT_INFILESIZE, $this->size);
1807 }
1808 elseif ($this->data !== false)
1809 {
1810 curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb);
1811 curl_setopt($curl, CURLOPT_POSTFIELDS, $this->data);
1812 }
1813 else
1814 curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb);
1815 break;
1816 case 'HEAD':
1817 curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'HEAD');
1818 curl_setopt($curl, CURLOPT_NOBODY, true);
1819 break;
1820 case 'DELETE':
1821 curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE');
1822 break;
1823 default: break;
1824 }
1825
1826 // Execute, grab errors
1827 if (curl_exec($curl))
1828 $this->response->code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
1829 else
1830 $this->response->error = array(
1831 'code' => curl_errno($curl),
1832 'message' => curl_error($curl),
1833 'resource' => $this->resource
1834 );
1835
1836 @curl_close($curl);
1837
1838 // Parse body into XML
1839 if ($this->response->error === false && isset($this->response->headers['type']) &&
1840 $this->response->headers['type'] == 'application/xml' && isset($this->response->body))
1841 {
1842 $this->response->body = simplexml_load_string($this->response->body);
1843
1844 // Grab S3 errors
1845 if (!in_array($this->response->code, array(200, 204, 206)) &&
1846 isset($this->response->body->Code, $this->response->body->Message))
1847 {
1848 $this->response->error = array(
1849 'code' => (string)$this->response->body->Code,
1850 'message' => (string)$this->response->body->Message
1851 );
1852 if (isset($this->response->body->Resource))
1853 $this->response->error['resource'] = (string)$this->response->body->Resource;
1854 unset($this->response->body);
1855 }
1856 }
1857
1858 // Clean up file resources
1859 if ($this->fp !== false && is_resource($this->fp)) fclose($this->fp);
1860
1861 return $this->response;
1862 }
1863
1864
1865 /**
1866 * CURL write callback
1867 *
1868 * @param resource &$curl CURL resource
1869 * @param string &$data Data
1870 * @return integer
1871 */
1872 private function __responseWriteCallback(&$curl, &$data)
1873 {
1874 if (in_array($this->response->code, array(200, 206)) && $this->fp !== false)
1875 return fwrite($this->fp, $data);
1876 else
1877 $this->response->body .= $data;
1878 return strlen($data);
1879 }
1880
1881
1882 /**
1883 * CURL header callback
1884 *
1885 * @param resource &$curl CURL resource
1886 * @param string &$data Data
1887 * @return integer
1888 */
1889 private function __responseHeaderCallback(&$curl, &$data)
1890 {
1891 if (($strlen = strlen($data)) <= 2) return $strlen;
1892 if (substr($data, 0, 4) == 'HTTP')
1893 $this->response->code = (int)substr($data, 9, 3);
1894 else
1895 {
1896 $data = trim($data);
1897 if (strpos($data, ': ') === false) return $strlen;
1898 list($header, $value) = explode(': ', $data, 2);
1899 if ($header == 'Last-Modified')
1900 $this->response->headers['time'] = strtotime($value);
1901 elseif ($header == 'Content-Length')
1902 $this->response->headers['size'] = (int)$value;
1903 elseif ($header == 'Content-Type')
1904 $this->response->headers['type'] = $value;
1905 elseif ($header == 'ETag')
1906 $this->response->headers['hash'] = $value{0} == '"' ? substr($value, 1, -1) : $value;
1907 elseif (preg_match('/^x-amz-meta-.*$/', $header))
1908 $this->response->headers[$header] = is_numeric($value) ? (int)$value : $value;
1909 }
1910 return $strlen;
1911 }
1912
1913 }
1914
1915 class S3Exception extends Exception {
1916 function __construct($message, $file, $line, $code = 0)
1917 {
1918 parent::__construct($message, $code);
1919 $this->file = $file;
1920 $this->line = $line;
1921 }
1922 }
1923