PluginProbe
ManageWP Worker / 3.9.13
ManageWP Worker v3.9.13
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.13, at lib/s3.php

1,966 lines 63.8 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', $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 $hostBucket ? $bucket : 's3.amazonaws.com/'.$bucket, $uri, self::$__accessKey, $expires,
972 urlencode(self::__getHash("GET\n\n\n{$expires}\n/{$bucket}/{$uri}")));
973 }
974
975
976 /**
977 * Get a CloudFront signed policy URL
978 *
979 * @param array $policy Policy
980 * @return string
981 */
982 public static function getSignedPolicyURL($policy)
983 {
984 $data = json_encode($policy);
985 $signature = '';
986 if (!openssl_sign($data, $signature, self::$__signingKeyResource)) return false;
987
988 $encoded = str_replace(array('+', '='), array('-', '_', '~'), base64_encode($data));
989 $signature = str_replace(array('+', '='), array('-', '_', '~'), base64_encode($signature));
990
991 $url = $policy['Statement'][0]['Resource'] . '?';
992 foreach (array('Policy' => $encoded, 'Signature' => $signature, 'Key-Pair-Id' => self::$__signingKeyPairId) as $k => $v)
993 $url .= $k.'='.str_replace('%2F', '/', rawurlencode($v)).'&';
994 return substr($url, 0, -1);
995 }
996
997
998 /**
999 * Get a CloudFront canned policy URL
1000 *
1001 * @param string $string URL to sign
1002 * @param integer $lifetime URL lifetime
1003 * @return string
1004 */
1005 public static function getSignedCannedURL($url, $lifetime)
1006 {
1007 return self::getSignedPolicyURL(array(
1008 'Statement' => array(
1009 array('Resource' => $url, 'Condition' => array(
1010 'DateLessThan' => array('AWS:EpochTime' => time() + $lifetime)
1011 ))
1012 )
1013 ));
1014 }
1015
1016
1017 /**
1018 * Get upload POST parameters for form uploads
1019 *
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 // $this->resource = '/'.$this->bucket.$this->uri;
1655 //else
1656 // $this->resource = $this->uri;
1657
1658 if ($this->bucket !== '')
1659 {
1660 if ($this->__dnsBucketName($this->bucket))
1661 {
1662 $this->headers['Host'] = $this->bucket.'.'.$this->endpoint;
1663 $this->resource = '/'.$this->bucket.$this->uri;
1664 }
1665 else
1666 {
1667 $this->headers['Host'] = $this->endpoint;
1668 $this->uri = $this->uri;
1669 if ($this->bucket !== '') $this->uri = '/'.$this->bucket.$this->uri;
1670 $this->bucket = '';
1671 $this->resource = $this->uri;
1672 }
1673 }
1674 else
1675 {
1676 $this->headers['Host'] = $this->endpoint;
1677 $this->resource = $this->uri;
1678 }
1679
1680
1681 $this->headers['Date'] = gmdate('D, d M Y H:i:s T');
1682 $this->response = new STDClass;
1683 $this->response->error = false;
1684 }
1685
1686
1687 /**
1688 * Set request parameter
1689 *
1690 * @param string $key Key
1691 * @param string $value Value
1692 * @return void
1693 */
1694 public function setParameter($key, $value)
1695 {
1696 $this->parameters[$key] = $value;
1697 }
1698
1699
1700 /**
1701 * Set request header
1702 *
1703 * @param string $key Key
1704 * @param string $value Value
1705 * @return void
1706 */
1707 public function setHeader($key, $value)
1708 {
1709 $this->headers[$key] = $value;
1710 }
1711
1712
1713 /**
1714 * Set x-amz-meta-* header
1715 *
1716 * @param string $key Key
1717 * @param string $value Value
1718 * @return void
1719 */
1720 public function setAmzHeader($key, $value)
1721 {
1722 $this->amzHeaders[$key] = $value;
1723 }
1724
1725
1726 /**
1727 * Get the S3 response
1728 *
1729 * @return object | false
1730 */
1731 public function getResponse()
1732 {
1733 $query = '';
1734 if (sizeof($this->parameters) > 0)
1735 {
1736 $query = substr($this->uri, -1) !== '?' ? '?' : '&';
1737 foreach ($this->parameters as $var => $value)
1738 if ($value == null || $value == '') $query .= $var.'&';
1739 // Parameters should be encoded (thanks Sean O'Dea)
1740 else $query .= $var.'='.rawurlencode($value).'&';
1741 $query = substr($query, 0, -1);
1742 $this->uri .= $query;
1743
1744 if (array_key_exists('acl', $this->parameters) ||
1745 array_key_exists('location', $this->parameters) ||
1746 array_key_exists('torrent', $this->parameters) ||
1747 array_key_exists('website', $this->parameters) ||
1748 array_key_exists('logging', $this->parameters))
1749 $this->resource .= $query;
1750 }
1751 $url = (S3::$useSSL ? 'https://' : 'http://') . ($this->headers['Host'] !== '' ? $this->headers['Host'] : $this->endpoint) . $this->uri;
1752
1753 //var_dump('bucket: ' . $this->bucket, 'uri: ' . $this->uri, 'resource: ' . $this->resource, 'url: ' . $url);
1754
1755 // Basic setup
1756 $curl = curl_init();
1757 curl_setopt($curl, CURLOPT_USERAGENT, 'S3/php');
1758
1759 if (S3::$useSSL)
1760 {
1761 // SSL Validation can now be optional for those with broken OpenSSL installations
1762 curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, S3::$useSSLValidation ? 1 : 0);
1763 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, S3::$useSSLValidation ? 1 : 0);
1764
1765 if (S3::$sslKey !== null) curl_setopt($curl, CURLOPT_SSLKEY, S3::$sslKey);
1766 if (S3::$sslCert !== null) curl_setopt($curl, CURLOPT_SSLCERT, S3::$sslCert);
1767 if (S3::$sslCACert !== null) curl_setopt($curl, CURLOPT_CAINFO, S3::$sslCACert);
1768 }
1769
1770 curl_setopt($curl, CURLOPT_URL, $url);
1771
1772 if (S3::$proxy != null && isset(S3::$proxy['host']))
1773 {
1774 curl_setopt($curl, CURLOPT_PROXY, S3::$proxy['host']);
1775 curl_setopt($curl, CURLOPT_PROXYTYPE, S3::$proxy['type']);
1776 if (isset(S3::$proxy['user'], S3::$proxy['pass']) && $proxy['user'] != null && $proxy['pass'] != null)
1777 curl_setopt($curl, CURLOPT_PROXYUSERPWD, sprintf('%s:%s', S3::$proxy['user'], S3::$proxy['pass']));
1778 }
1779
1780 // Headers
1781 $headers = array(); $amz = array();
1782 foreach ($this->amzHeaders as $header => $value)
1783 if (strlen($value) > 0) $headers[] = $header.': '.$value;
1784 foreach ($this->headers as $header => $value)
1785 if (strlen($value) > 0) $headers[] = $header.': '.$value;
1786
1787 // Collect AMZ headers for signature
1788 foreach ($this->amzHeaders as $header => $value)
1789 if (strlen($value) > 0) $amz[] = strtolower($header).':'.$value;
1790
1791 // AMZ headers must be sorted
1792 if (sizeof($amz) > 0)
1793 {
1794 sort($amz);
1795 $amz = "\n".implode("\n", $amz);
1796 } else $amz = '';
1797
1798 if (S3::hasAuth())
1799 {
1800 // Authorization string (CloudFront stringToSign should only contain a date)
1801 if ($this->headers['Host'] == 'cloudfront.amazonaws.com')
1802 $headers[] = 'Authorization: ' . S3::__getSignature($this->headers['Date']);
1803 else
1804 {
1805 $headers[] = 'Authorization: ' . S3::__getSignature(
1806 $this->verb."\n".
1807 $this->headers['Content-MD5']."\n".
1808 $this->headers['Content-Type']."\n".
1809 $this->headers['Date'].$amz."\n".
1810 $this->resource
1811 );
1812 }
1813 }
1814
1815 curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
1816 curl_setopt($curl, CURLOPT_HEADER, false);
1817 curl_setopt($curl, CURLOPT_RETURNTRANSFER, false);
1818 curl_setopt($curl, CURLOPT_WRITEFUNCTION, array(&$this, '__responseWriteCallback'));
1819 curl_setopt($curl, CURLOPT_HEADERFUNCTION, array(&$this, '__responseHeaderCallback'));
1820 curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
1821
1822 // Request types
1823 switch ($this->verb)
1824 {
1825 case 'GET': break;
1826 case 'PUT': case 'POST': // POST only used for CloudFront
1827 if ($this->fp !== false)
1828 {
1829 curl_setopt($curl, CURLOPT_PUT, true);
1830 curl_setopt($curl, CURLOPT_INFILE, $this->fp);
1831 if ($this->size >= 0)
1832 curl_setopt($curl, CURLOPT_INFILESIZE, $this->size);
1833 }
1834 elseif ($this->data !== false)
1835 {
1836 curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb);
1837 curl_setopt($curl, CURLOPT_POSTFIELDS, $this->data);
1838 }
1839 else
1840 curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb);
1841 break;
1842 case 'HEAD':
1843 curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'HEAD');
1844 curl_setopt($curl, CURLOPT_NOBODY, true);
1845 break;
1846 case 'DELETE':
1847 curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE');
1848 break;
1849 default: break;
1850 }
1851
1852 // Execute, grab errors
1853 if (curl_exec($curl))
1854 $this->response->code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
1855 else
1856 $this->response->error = array(
1857 'code' => curl_errno($curl),
1858 'message' => curl_error($curl),
1859 'resource' => $this->resource
1860 );
1861
1862 @curl_close($curl);
1863
1864 // Parse body into XML
1865 if ($this->response->error === false && isset($this->response->headers['type']) &&
1866 $this->response->headers['type'] == 'application/xml' && isset($this->response->body))
1867 {
1868 $this->response->body = simplexml_load_string($this->response->body);
1869
1870 // Grab S3 errors
1871 if (!in_array($this->response->code, array(200, 204, 206)) &&
1872 isset($this->response->body->Code, $this->response->body->Message))
1873 {
1874 $this->response->error = array(
1875 'code' => (string)$this->response->body->Code,
1876 'message' => (string)$this->response->body->Message
1877 );
1878 if (isset($this->response->body->Resource))
1879 $this->response->error['resource'] = (string)$this->response->body->Resource;
1880 unset($this->response->body);
1881 }
1882 }
1883
1884 // Clean up file resources
1885 if ($this->fp !== false && is_resource($this->fp)) fclose($this->fp);
1886
1887 return $this->response;
1888 }
1889
1890
1891 /**
1892 * CURL write callback
1893 *
1894 * @param resource &$curl CURL resource
1895 * @param string &$data Data
1896 * @return integer
1897 */
1898 private function __responseWriteCallback(&$curl, &$data)
1899 {
1900 if (in_array($this->response->code, array(200, 206)) && $this->fp !== false)
1901 return fwrite($this->fp, $data);
1902 else
1903 $this->response->body .= $data;
1904 return strlen($data);
1905 }
1906
1907
1908 /**
1909 * Check DNS conformity
1910 *
1911 * @param string $bucket Bucket name
1912 * @return boolean
1913 */
1914 private function __dnsBucketName($bucket)
1915 {
1916 if (strlen($bucket) > 63 || !preg_match("/[^a-z0-9\.-]/", $bucket)) return false;
1917 if (strstr($bucket, '-.') !== false) return false;
1918 if (strstr($bucket, '..') !== false) return false;
1919 if (!preg_match("/^[0-9a-z]/", $bucket)) return false;
1920 if (!preg_match("/[0-9a-z]$/", $bucket)) return false;
1921 return true;
1922 }
1923
1924
1925 /**
1926 * CURL header callback
1927 *
1928 * @param resource &$curl CURL resource
1929 * @param string &$data Data
1930 * @return integer
1931 */
1932 private function __responseHeaderCallback(&$curl, &$data)
1933 {
1934 if (($strlen = strlen($data)) <= 2) return $strlen;
1935 if (substr($data, 0, 4) == 'HTTP')
1936 $this->response->code = (int)substr($data, 9, 3);
1937 else
1938 {
1939 $data = trim($data);
1940 if (strpos($data, ': ') === false) return $strlen;
1941 list($header, $value) = explode(': ', $data, 2);
1942 if ($header == 'Last-Modified')
1943 $this->response->headers['time'] = strtotime($value);
1944 elseif ($header == 'Content-Length')
1945 $this->response->headers['size'] = (int)$value;
1946 elseif ($header == 'Content-Type')
1947 $this->response->headers['type'] = $value;
1948 elseif ($header == 'ETag')
1949 $this->response->headers['hash'] = $value{0} == '"' ? substr($value, 1, -1) : $value;
1950 elseif (preg_match('/^x-amz-meta-.*$/', $header))
1951 $this->response->headers[$header] = is_numeric($value) ? (int)$value : $value;
1952 }
1953 return $strlen;
1954 }
1955
1956 }
1957
1958 class S3Exception extends Exception {
1959 function __construct($message, $file, $line, $code = 0)
1960 {
1961 parent::__construct($message, $code);
1962 $this->file = $file;
1963 $this->line = $line;
1964 }
1965 }
1966