PluginProbe
ManageWP Worker / 3.9.24
ManageWP Worker v3.9.24
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.24, at lib/s3.php

1,968 lines 66.0 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 mwpS3
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('mwpS3::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
220 //if (self::$useExceptions)
221 throw new mwpS3Exception($message, $file, $line, $code);
222 //else
223 //trigger_error($message, E_USER_WARNING);
224 }
225
226
227 /**
228 * Get a list of buckets
229 *
230 * @param boolean $detailed Returns detailed bucket list when true
231 * @return array | false
232 */
233 public static function listBuckets($detailed = false)
234 {
235 $rest = new mwpS3Request('GET', '', '', self::$endpoint);
236 $rest = $rest->getResponse();
237 if ($rest->error === false && $rest->code !== 200)
238 $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
239 if ($rest->error !== false)
240 {
241 self::__triggerError(sprintf("mwpS3::listBuckets(): [%s] %s", $rest->error['code'],
242 $rest->error['message']), __FILE__, __LINE__);
243 return false;
244 }
245 $results = array();
246 if (!isset($rest->body->Buckets)) return $results;
247
248 if ($detailed)
249 {
250 if (isset($rest->body->Owner, $rest->body->Owner->ID, $rest->body->Owner->DisplayName))
251 $results['owner'] = array(
252 'id' => (string)$rest->body->Owner->ID, 'name' => (string)$rest->body->Owner->ID
253 );
254 $results['buckets'] = array();
255 foreach ($rest->body->Buckets->Bucket as $b)
256 $results['buckets'][] = array(
257 'name' => (string)$b->Name, 'time' => strtotime((string)$b->CreationDate)
258 );
259 } else
260 foreach ($rest->body->Buckets->Bucket as $b) $results[] = (string)$b->Name;
261
262 return $results;
263 }
264
265
266 /*
267 * Get contents for a bucket
268 *
269 * If maxKeys is null this method will loop through truncated result sets
270 *
271 * @param string $bucket Bucket name
272 * @param string $prefix Prefix
273 * @param string $marker Marker (last file listed)
274 * @param string $maxKeys Max keys (maximum number of keys to return)
275 * @param string $delimiter Delimiter
276 * @param boolean $returnCommonPrefixes Set to true to return CommonPrefixes
277 * @return array | false
278 */
279 public static function getBucket($bucket, $prefix = null, $marker = null, $maxKeys = null, $delimiter = null, $returnCommonPrefixes = false)
280 {
281 $rest = new mwpS3Request('GET', $bucket, '', self::$endpoint);
282 if ($maxKeys == 0) $maxKeys = null;
283 if ($prefix !== null && $prefix !== '') $rest->setParameter('prefix', $prefix);
284 if ($marker !== null && $marker !== '') $rest->setParameter('marker', $marker);
285 if ($maxKeys !== null && $maxKeys !== '') $rest->setParameter('max-keys', $maxKeys);
286 if ($delimiter !== null && $delimiter !== '') $rest->setParameter('delimiter', $delimiter);
287 $response = $rest->getResponse();
288 if ($response->error === false && $response->code !== 200)
289 $response->error = array('code' => $response->code, 'message' => 'Unexpected HTTP status');
290 if ($response->error !== false)
291 {
292 self::__triggerError(sprintf("mwpS3::getBucket(): [%s] %s",
293 $response->error['code'], $response->error['message']), __FILE__, __LINE__);
294 return false;
295 }
296
297 $results = array();
298
299 $nextMarker = null;
300 if (isset($response->body, $response->body->Contents))
301 foreach ($response->body->Contents as $c)
302 {
303 $results[(string)$c->Key] = array(
304 'name' => (string)$c->Key,
305 'time' => strtotime((string)$c->LastModified),
306 'size' => (int)$c->Size,
307 'hash' => substr((string)$c->ETag, 1, -1)
308 );
309 $nextMarker = (string)$c->Key;
310 }
311
312 if ($returnCommonPrefixes && isset($response->body, $response->body->CommonPrefixes))
313 foreach ($response->body->CommonPrefixes as $c)
314 $results[(string)$c->Prefix] = array('prefix' => (string)$c->Prefix);
315
316 if (isset($response->body, $response->body->IsTruncated) &&
317 (string)$response->body->IsTruncated == 'false') return $results;
318
319 if (isset($response->body, $response->body->NextMarker))
320 $nextMarker = (string)$response->body->NextMarker;
321
322 // Loop through truncated results if maxKeys isn't specified
323 if ($maxKeys == null && $nextMarker !== null && (string)$response->body->IsTruncated == 'true')
324 do
325 {
326 $rest = new mwpS3Request('GET', $bucket, '', self::$endpoint);
327 if ($prefix !== null && $prefix !== '') $rest->setParameter('prefix', $prefix);
328 $rest->setParameter('marker', $nextMarker);
329 if ($delimiter !== null && $delimiter !== '') $rest->setParameter('delimiter', $delimiter);
330
331 if (($response = $rest->getResponse(true)) == false || $response->code !== 200) break;
332
333 if (isset($response->body, $response->body->Contents))
334 foreach ($response->body->Contents as $c)
335 {
336 $results[(string)$c->Key] = array(
337 'name' => (string)$c->Key,
338 'time' => strtotime((string)$c->LastModified),
339 'size' => (int)$c->Size,
340 'hash' => substr((string)$c->ETag, 1, -1)
341 );
342 $nextMarker = (string)$c->Key;
343 }
344
345 if ($returnCommonPrefixes && isset($response->body, $response->body->CommonPrefixes))
346 foreach ($response->body->CommonPrefixes as $c)
347 $results[(string)$c->Prefix] = array('prefix' => (string)$c->Prefix);
348
349 if (isset($response->body, $response->body->NextMarker))
350 $nextMarker = (string)$response->body->NextMarker;
351
352 } while ($response !== false && (string)$response->body->IsTruncated == 'true');
353
354 return $results;
355 }
356
357
358 /**
359 * Put a bucket
360 *
361 * @param string $bucket Bucket name
362 * @param constant $acl ACL flag
363 * @param string $location Set as "EU" to create buckets hosted in Europe
364 * @return boolean
365 */
366 public static function putBucket($bucket, $acl = self::ACL_PRIVATE, $location = false)
367 {
368 $rest = new mwpS3Request('PUT', $bucket, '', self::$endpoint);
369 $rest->setAmzHeader('x-amz-acl', $acl);
370
371 if ($location !== false)
372 {
373 $dom = new DOMDocument;
374 $createBucketConfiguration = $dom->createElement('CreateBucketConfiguration');
375 $locationConstraint = $dom->createElement('LocationConstraint', $location);
376 $createBucketConfiguration->appendChild($locationConstraint);
377 $dom->appendChild($createBucketConfiguration);
378 $rest->data = $dom->saveXML();
379 $rest->size = strlen($rest->data);
380 $rest->setHeader('Content-Type', 'application/xml');
381 }
382 $rest = $rest->getResponse();
383
384 if ($rest->error === false && $rest->code !== 200)
385 $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
386 if ($rest->error !== false)
387 {
388 self::__triggerError(sprintf("mwpS3::putBucket({$bucket}, {$acl}, {$location}): [%s] %s",
389 $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
390 return false;
391 }
392 return true;
393 }
394
395
396 /**
397 * Delete an empty bucket
398 *
399 * @param string $bucket Bucket name
400 * @return boolean
401 */
402 public static function deleteBucket($bucket)
403 {
404 $rest = new mwpS3Request('DELETE', $bucket, '', self::$endpoint);
405 $rest = $rest->getResponse();
406 if ($rest->error === false && $rest->code !== 204)
407 $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
408 if ($rest->error !== false)
409 {
410 self::__triggerError(sprintf("mwpS3::deleteBucket({$bucket}): [%s] %s",
411 $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
412 return false;
413 }
414 return true;
415 }
416
417
418 /**
419 * Create input info array for putObject()
420 *
421 * @param string $file Input file
422 * @param mixed $md5sum Use MD5 hash (supply a string if you want to use your own)
423 * @return array | false
424 */
425 public static function inputFile($file, $md5sum = true)
426 {
427 if (!file_exists($file) || !is_file($file) || !is_readable($file))
428 {
429 self::__triggerError('mwpS3::inputFile(): Unable to open input file: '.$file, __FILE__, __LINE__);
430 return false;
431 }
432 return array('file' => $file, 'size' => filesize($file), 'md5sum' => $md5sum !== false ?
433 (is_string($md5sum) ? $md5sum : base64_encode(md5_file($file, true))) : '');
434 }
435
436
437 /**
438 * Create input array info for putObject() with a resource
439 *
440 * @param string $resource Input resource to read from
441 * @param integer $bufferSize Input byte size
442 * @param string $md5sum MD5 hash to send (optional)
443 * @return array | false
444 */
445 public static function inputResource(&$resource, $bufferSize, $md5sum = '')
446 {
447 if (!is_resource($resource) || $bufferSize < 0)
448 {
449 self::__triggerError('mwpS3::inputResource(): Invalid resource or buffer size', __FILE__, __LINE__);
450 return false;
451 }
452 $input = array('size' => $bufferSize, 'md5sum' => $md5sum);
453 $input['fp'] =& $resource;
454 return $input;
455 }
456
457
458 /**
459 * Put an object
460 *
461 * @param mixed $input Input data
462 * @param string $bucket Bucket name
463 * @param string $uri Object URI
464 * @param constant $acl ACL constant
465 * @param array $metaHeaders Array of x-amz-meta-* headers
466 * @param array $requestHeaders Array of request headers or content type as a string
467 * @param constant $storageClass Storage class constant
468 * @return boolean
469 */
470 public static function putObject($input, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD)
471 {
472 if ($input === false) return false;
473 $rest = new mwpS3Request('PUT', $bucket, $uri, self::$endpoint);
474
475 if (is_string($input)) $input = array(
476 'data' => $input, 'size' => strlen($input),
477 'md5sum' => base64_encode(md5($input, true))
478 );
479
480 // Data
481 if (isset($input['fp']))
482 $rest->fp =& $input['fp'];
483 elseif (isset($input['file']))
484 $rest->fp = @fopen($input['file'], 'rb');
485 elseif (isset($input['data']))
486 $rest->data = $input['data'];
487
488 // Content-Length (required)
489 if (isset($input['size']) && $input['size'] >= 0)
490 $rest->size = $input['size'];
491 else {
492 if (isset($input['file']))
493 $rest->size = filesize($input['file']);
494 elseif (isset($input['data']))
495 $rest->size = strlen($input['data']);
496 }
497
498 // Custom request headers (Content-Type, Content-Disposition, Content-Encoding)
499 if (is_array($requestHeaders))
500 foreach ($requestHeaders as $h => $v) $rest->setHeader($h, $v);
501 elseif (is_string($requestHeaders)) // Support for legacy contentType parameter
502 $input['type'] = $requestHeaders;
503
504 // Content-Type
505 if (!isset($input['type']))
506 {
507 if (isset($requestHeaders['Content-Type']))
508 $input['type'] =& $requestHeaders['Content-Type'];
509 elseif (isset($input['file']))
510 $input['type'] = self::__getMimeType($input['file']);
511 else
512 $input['type'] = 'application/octet-stream';
513 }
514
515 if ($storageClass !== self::STORAGE_CLASS_STANDARD) // Storage class
516 $rest->setAmzHeader('x-amz-storage-class', $storageClass);
517
518 // We need to post with Content-Length and Content-Type, MD5 is optional
519 if ($rest->size >= 0 && ($rest->fp !== false || $rest->data !== false))
520 {
521 $rest->setHeader('Content-Type', $input['type']);
522 if (isset($input['md5sum'])) $rest->setHeader('Content-MD5', $input['md5sum']);
523
524 $rest->setAmzHeader('x-amz-acl', $acl);
525 foreach ($metaHeaders as $h => $v) $rest->setAmzHeader('x-amz-meta-'.$h, $v);
526 $rest->getResponse();
527 } else
528 $rest->response->error = array('code' => 0, 'message' => 'Missing input parameters');
529
530 if ($rest->response->error === false && $rest->response->code !== 200)
531 $rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status');
532 if ($rest->response->error !== false)
533 {
534 self::__triggerError(sprintf("mwpS3::putObject(): [%s] %s",
535 $rest->response->error['code'], $rest->response->error['message']), __FILE__, __LINE__);
536 return false;
537 }
538 return true;
539 }
540
541
542 /**
543 * Put an object from a file (legacy function)
544 *
545 * @param string $file Input file path
546 * @param string $bucket Bucket name
547 * @param string $uri Object URI
548 * @param constant $acl ACL constant
549 * @param array $metaHeaders Array of x-amz-meta-* headers
550 * @param string $contentType Content type
551 * @return boolean
552 */
553 public static function putObjectFile($file, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = null)
554 {
555 return self::putObject(self::inputFile($file), $bucket, $uri, $acl, $metaHeaders, $contentType);
556 }
557
558
559 /**
560 * Put an object from a string (legacy function)
561 *
562 * @param string $string Input data
563 * @param string $bucket Bucket name
564 * @param string $uri Object URI
565 * @param constant $acl ACL constant
566 * @param array $metaHeaders Array of x-amz-meta-* headers
567 * @param string $contentType Content type
568 * @return boolean
569 */
570 public static function putObjectString($string, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = 'text/plain')
571 {
572 return self::putObject($string, $bucket, $uri, $acl, $metaHeaders, $contentType);
573 }
574
575
576 /**
577 * Get an object
578 *
579 * @param string $bucket Bucket name
580 * @param string $uri Object URI
581 * @param mixed $saveTo Filename or resource to write to
582 * @return mixed
583 */
584 public static function getObject($bucket, $uri, $saveTo = false)
585 {
586 $rest = new mwpS3Request('GET', $bucket, $uri, self::$endpoint);
587 if ($saveTo !== false)
588 {
589 if (is_resource($saveTo))
590 $rest->fp =& $saveTo;
591 else
592 if (($rest->fp = @fopen($saveTo, 'wb')) !== false)
593 $rest->file = realpath($saveTo);
594 else
595 $rest->response->error = array('code' => 0, 'message' => 'Unable to open save file for writing: '.$saveTo);
596 }
597 if ($rest->response->error === false) $rest->getResponse();
598
599 if ($rest->response->error === false && $rest->response->code !== 200)
600 $rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status');
601 if ($rest->response->error !== false)
602 {
603 self::__triggerError(sprintf("mwpS3::getObject({$bucket}, {$uri}): [%s] %s",
604 $rest->response->error['code'], $rest->response->error['message']), __FILE__, __LINE__);
605 return false;
606 }
607 return $rest->response;
608 }
609
610
611 /**
612 * Get object information
613 *
614 * @param string $bucket Bucket name
615 * @param string $uri Object URI
616 * @param boolean $returnInfo Return response information
617 * @return mixed | false
618 */
619 public static function getObjectInfo($bucket, $uri, $returnInfo = true)
620 {
621 $rest = new mwpS3Request('HEAD', $bucket, $uri, self::$endpoint);
622 $rest = $rest->getResponse();
623 if ($rest->error === false && ($rest->code !== 200 && $rest->code !== 404))
624 $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
625 if ($rest->error !== false)
626 {
627 self::__triggerError(sprintf("mwpS3::getObjectInfo({$bucket}, {$uri}): [%s] %s",
628 $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
629 return false;
630 }
631 return $rest->code == 200 ? $returnInfo ? $rest->headers : true : false;
632 }
633
634
635 /**
636 * Copy an object
637 *
638 * @param string $bucket Source bucket name
639 * @param string $uri Source object URI
640 * @param string $bucket Destination bucket name
641 * @param string $uri Destination object URI
642 * @param constant $acl ACL constant
643 * @param array $metaHeaders Optional array of x-amz-meta-* headers
644 * @param array $requestHeaders Optional array of request headers (content type, disposition, etc.)
645 * @param constant $storageClass Storage class constant
646 * @return mixed | false
647 */
648 public static function copyObject($srcBucket, $srcUri, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD)
649 {
650 $rest = new mwpS3Request('PUT', $bucket, $uri, self::$endpoint);
651 $rest->setHeader('Content-Length', 0);
652 foreach ($requestHeaders as $h => $v) $rest->setHeader($h, $v);
653 foreach ($metaHeaders as $h => $v) $rest->setAmzHeader('x-amz-meta-'.$h, $v);
654 if ($storageClass !== self::STORAGE_CLASS_STANDARD) // Storage class
655 $rest->setAmzHeader('x-amz-storage-class', $storageClass);
656 $rest->setAmzHeader('x-amz-acl', $acl); // Added rawurlencode() for $srcUri (thanks a.yamanoi)
657 $rest->setAmzHeader('x-amz-copy-source', sprintf('/%s/%s', $srcBucket, rawurlencode($srcUri)));
658 if (sizeof($requestHeaders) > 0 || sizeof($metaHeaders) > 0)
659 $rest->setAmzHeader('x-amz-metadata-directive', 'REPLACE');
660
661 $rest = $rest->getResponse();
662 if ($rest->error === false && $rest->code !== 200)
663 $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
664 if ($rest->error !== false)
665 {
666 self::__triggerError(sprintf("mwpS3::copyObject({$srcBucket}, {$srcUri}, {$bucket}, {$uri}): [%s] %s",
667 $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
668 return false;
669 }
670 return isset($rest->body->LastModified, $rest->body->ETag) ? array(
671 'time' => strtotime((string)$rest->body->LastModified),
672 'hash' => substr((string)$rest->body->ETag, 1, -1)
673 ) : false;
674 }
675
676
677 /**
678 * Set logging for a bucket
679 *
680 * @param string $bucket Bucket name
681 * @param string $targetBucket Target bucket (where logs are stored)
682 * @param string $targetPrefix Log prefix (e,g; domain.com-)
683 * @return boolean
684 */
685 public static function setBucketLogging($bucket, $targetBucket, $targetPrefix = null)
686 {
687 // The S3 log delivery group has to be added to the target bucket's ACP
688 if ($targetBucket !== null && ($acp = self::getAccessControlPolicy($targetBucket, '')) !== false)
689 {
690 // Only add permissions to the target bucket when they do not exist
691 $aclWriteSet = false;
692 $aclReadSet = false;
693 foreach ($acp['acl'] as $acl)
694 if ($acl['type'] == 'Group' && $acl['uri'] == 'http://acs.amazonaws.com/groups/s3/LogDelivery')
695 {
696 if ($acl['permission'] == 'WRITE') $aclWriteSet = true;
697 elseif ($acl['permission'] == 'READ_ACP') $aclReadSet = true;
698 }
699 if (!$aclWriteSet) $acp['acl'][] = array(
700 'type' => 'Group', 'uri' => 'http://acs.amazonaws.com/groups/s3/LogDelivery', 'permission' => 'WRITE'
701 );
702 if (!$aclReadSet) $acp['acl'][] = array(
703 'type' => 'Group', 'uri' => 'http://acs.amazonaws.com/groups/s3/LogDelivery', 'permission' => 'READ_ACP'
704 );
705 if (!$aclReadSet || !$aclWriteSet) self::setAccessControlPolicy($targetBucket, '', $acp);
706 }
707
708 $dom = new DOMDocument;
709 $bucketLoggingStatus = $dom->createElement('BucketLoggingStatus');
710 $bucketLoggingStatus->setAttribute('xmlns', 'http://s3.amazonaws.com/doc/2006-03-01/');
711 if ($targetBucket !== null)
712 {
713 if ($targetPrefix == null) $targetPrefix = $bucket . '-';
714 $loggingEnabled = $dom->createElement('LoggingEnabled');
715 $loggingEnabled->appendChild($dom->createElement('TargetBucket', $targetBucket));
716 $loggingEnabled->appendChild($dom->createElement('TargetPrefix', $targetPrefix));
717 // TODO: Add TargetGrants?
718 $bucketLoggingStatus->appendChild($loggingEnabled);
719 }
720 $dom->appendChild($bucketLoggingStatus);
721
722 $rest = new mwpS3Request('PUT', $bucket, '', self::$endpoint);
723 $rest->setParameter('logging', null);
724 $rest->data = $dom->saveXML();
725 $rest->size = strlen($rest->data);
726 $rest->setHeader('Content-Type', 'application/xml');
727 $rest = $rest->getResponse();
728 if ($rest->error === false && $rest->code !== 200)
729 $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
730 if ($rest->error !== false)
731 {
732 self::__triggerError(sprintf("mwpS3::setBucketLogging({$bucket}, {$uri}): [%s] %s",
733 $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
734 return false;
735 }
736 return true;
737 }
738
739
740 /**
741 * Get logging status for a bucket
742 *
743 * This will return false if logging is not enabled.
744 * Note: To enable logging, you also need to grant write access to the log group
745 *
746 * @param string $bucket Bucket name
747 * @return array | false
748 */
749 public static function getBucketLogging($bucket)
750 {
751 $rest = new mwpS3Request('GET', $bucket, '', self::$endpoint);
752 $rest->setParameter('logging', null);
753 $rest = $rest->getResponse();
754 if ($rest->error === false && $rest->code !== 200)
755 $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
756 if ($rest->error !== false)
757 {
758 self::__triggerError(sprintf("mwpS3::getBucketLogging({$bucket}): [%s] %s",
759 $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
760 return false;
761 }
762 if (!isset($rest->body->LoggingEnabled)) return false; // No logging
763 return array(
764 'targetBucket' => (string)$rest->body->LoggingEnabled->TargetBucket,
765 'targetPrefix' => (string)$rest->body->LoggingEnabled->TargetPrefix,
766 );
767 }
768
769
770 /**
771 * Disable bucket logging
772 *
773 * @param string $bucket Bucket name
774 * @return boolean
775 */
776 public static function disableBucketLogging($bucket)
777 {
778 return self::setBucketLogging($bucket, null);
779 }
780
781
782 /**
783 * Get a bucket's location
784 *
785 * @param string $bucket Bucket name
786 * @return string | false
787 */
788 public static function getBucketLocation($bucket)
789 {
790 $rest = new mwpS3Request('GET', $bucket, '', self::$endpoint);
791 $rest->setParameter('location', null);
792 $rest = $rest->getResponse();
793 if ($rest->error === false && $rest->code !== 200)
794 $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
795 if ($rest->error !== false)
796 {
797 self::__triggerError(sprintf("mwpS3::getBucketLocation({$bucket}): [%s] %s",
798 $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
799 return false;
800 }
801 return (isset($rest->body[0]) && (string)$rest->body[0] !== '') ? (string)$rest->body[0] : 'US';
802 }
803
804
805 /**
806 * Set object or bucket Access Control Policy
807 *
808 * @param string $bucket Bucket name
809 * @param string $uri Object URI
810 * @param array $acp Access Control Policy Data (same as the data returned from getAccessControlPolicy)
811 * @return boolean
812 */
813 public static function setAccessControlPolicy($bucket, $uri = '', $acp = array())
814 {
815 $dom = new DOMDocument;
816 $dom->formatOutput = true;
817 $accessControlPolicy = $dom->createElement('AccessControlPolicy');
818 $accessControlList = $dom->createElement('AccessControlList');
819
820 // It seems the owner has to be passed along too
821 $owner = $dom->createElement('Owner');
822 $owner->appendChild($dom->createElement('ID', $acp['owner']['id']));
823 $owner->appendChild($dom->createElement('DisplayName', $acp['owner']['name']));
824 $accessControlPolicy->appendChild($owner);
825
826 foreach ($acp['acl'] as $g)
827 {
828 $grant = $dom->createElement('Grant');
829 $grantee = $dom->createElement('Grantee');
830 $grantee->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
831 if (isset($g['id']))
832 { // CanonicalUser (DisplayName is omitted)
833 $grantee->setAttribute('xsi:type', 'CanonicalUser');
834 $grantee->appendChild($dom->createElement('ID', $g['id']));
835 }
836 elseif (isset($g['email']))
837 { // AmazonCustomerByEmail
838 $grantee->setAttribute('xsi:type', 'AmazonCustomerByEmail');
839 $grantee->appendChild($dom->createElement('EmailAddress', $g['email']));
840 }
841 elseif ($g['type'] == 'Group')
842 { // Group
843 $grantee->setAttribute('xsi:type', 'Group');
844 $grantee->appendChild($dom->createElement('URI', $g['uri']));
845 }
846 $grant->appendChild($grantee);
847 $grant->appendChild($dom->createElement('Permission', $g['permission']));
848 $accessControlList->appendChild($grant);
849 }
850
851 $accessControlPolicy->appendChild($accessControlList);
852 $dom->appendChild($accessControlPolicy);
853
854 $rest = new mwpS3Request('PUT', $bucket, $uri, self::$endpoint);
855 $rest->setParameter('acl', null);
856 $rest->data = $dom->saveXML();
857 $rest->size = strlen($rest->data);
858 $rest->setHeader('Content-Type', 'application/xml');
859 $rest = $rest->getResponse();
860 if ($rest->error === false && $rest->code !== 200)
861 $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
862 if ($rest->error !== false)
863 {
864 self::__triggerError(sprintf("mwpS3::setAccessControlPolicy({$bucket}, {$uri}): [%s] %s",
865 $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
866 return false;
867 }
868 return true;
869 }
870
871
872 /**
873 * Get object or bucket Access Control Policy
874 *
875 * @param string $bucket Bucket name
876 * @param string $uri Object URI
877 * @return mixed | false
878 */
879 public static function getAccessControlPolicy($bucket, $uri = '')
880 {
881 $rest = new mwpS3Request('GET', $bucket, $uri, self::$endpoint);
882 $rest->setParameter('acl', null);
883 $rest = $rest->getResponse();
884 if ($rest->error === false && $rest->code !== 200)
885 $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
886 if ($rest->error !== false)
887 {
888 self::__triggerError(sprintf("mwpS3::getAccessControlPolicy({$bucket}, {$uri}): [%s] %s",
889 $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
890 return false;
891 }
892
893 $acp = array();
894 if (isset($rest->body->Owner, $rest->body->Owner->ID, $rest->body->Owner->DisplayName))
895 $acp['owner'] = array(
896 'id' => (string)$rest->body->Owner->ID, 'name' => (string)$rest->body->Owner->DisplayName
897 );
898
899 if (isset($rest->body->AccessControlList))
900 {
901 $acp['acl'] = array();
902 foreach ($rest->body->AccessControlList->Grant as $grant)
903 {
904 foreach ($grant->Grantee as $grantee)
905 {
906 if (isset($grantee->ID, $grantee->DisplayName)) // CanonicalUser
907 $acp['acl'][] = array(
908 'type' => 'CanonicalUser',
909 'id' => (string)$grantee->ID,
910 'name' => (string)$grantee->DisplayName,
911 'permission' => (string)$grant->Permission
912 );
913 elseif (isset($grantee->EmailAddress)) // AmazonCustomerByEmail
914 $acp['acl'][] = array(
915 'type' => 'AmazonCustomerByEmail',
916 'email' => (string)$grantee->EmailAddress,
917 'permission' => (string)$grant->Permission
918 );
919 elseif (isset($grantee->URI)) // Group
920 $acp['acl'][] = array(
921 'type' => 'Group',
922 'uri' => (string)$grantee->URI,
923 'permission' => (string)$grant->Permission
924 );
925 else continue;
926 }
927 }
928 }
929 return $acp;
930 }
931
932
933 /**
934 * Delete an object
935 *
936 * @param string $bucket Bucket name
937 * @param string $uri Object URI
938 * @return boolean
939 */
940 public static function deleteObject($bucket, $uri)
941 {
942 $rest = new mwpS3Request('DELETE', $bucket, $uri, self::$endpoint);
943 $rest = $rest->getResponse();
944 if ($rest->error === false && $rest->code !== 204)
945 $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
946 if ($rest->error !== false)
947 {
948 self::__triggerError(sprintf("mwpS3::deleteObject(): [%s] %s",
949 $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
950 return false;
951 }
952 return true;
953 }
954
955
956 /**
957 * Get a query string authenticated URL
958 *
959 * @param string $bucket Bucket name
960 * @param string $uri Object URI
961 * @param integer $lifetime Lifetime in seconds
962 * @param boolean $hostBucket Use the bucket name as the hostname
963 * @param boolean $https Use HTTPS ($hostBucket should be false for SSL verification)
964 * @return string
965 */
966 public static function getAuthenticatedURL($bucket, $uri, $lifetime, $hostBucket = false, $https = false)
967 {
968 $expires = time() + $lifetime;
969 $uri = str_replace('%2F', '/', rawurlencode($uri)); // URI should be encoded (thanks Sean O'Dea)
970 return sprintf(($https ? 'https' : 'http').'://%s/%s?AWSAccessKeyId=%s&Expires=%u&Signature=%s',
971 // $hostBucket ? $bucket : $bucket.'.s3.amazonaws.com', $uri, self::$__accessKey, $expires,
972 $hostBucket ? $bucket : 's3.amazonaws.com/'.$bucket, $uri, self::$__accessKey, $expires,
973 urlencode(self::__getHash("GET\n\n\n{$expires}\n/{$bucket}/{$uri}")));
974 }
975
976
977 /**
978 * Get a CloudFront signed policy URL
979 *
980 * @param array $policy Policy
981 * @return string
982 */
983 public static function getSignedPolicyURL($policy)
984 {
985 $data = json_encode($policy);
986 $signature = '';
987 if (!openssl_sign($data, $signature, self::$__signingKeyResource)) return false;
988
989 $encoded = str_replace(array('+', '='), array('-', '_', '~'), base64_encode($data));
990 $signature = str_replace(array('+', '='), array('-', '_', '~'), base64_encode($signature));
991
992 $url = $policy['Statement'][0]['Resource'] . '?';
993 foreach (array('Policy' => $encoded, 'Signature' => $signature, 'Key-Pair-Id' => self::$__signingKeyPairId) as $k => $v)
994 $url .= $k.'='.str_replace('%2F', '/', rawurlencode($v)).'&';
995 return substr($url, 0, -1);
996 }
997
998
999 /**
1000 * Get a CloudFront canned policy URL
1001 *
1002 * @param string $string URL to sign
1003 * @param integer $lifetime URL lifetime
1004 * @return string
1005 */
1006 public static function getSignedCannedURL($url, $lifetime)
1007 {
1008 return self::getSignedPolicyURL(array(
1009 'Statement' => array(
1010 array('Resource' => $url, 'Condition' => array(
1011 'DateLessThan' => array('AWS:EpochTime' => time() + $lifetime)
1012 ))
1013 )
1014 ));
1015 }
1016
1017
1018 /**
1019 * Get upload POST parameters for form uploads
1020 *
1021 * @param string $bucket Bucket name
1022 * @param string $uriPrefix Object URI prefix
1023 * @param constant $acl ACL constant
1024 * @param integer $lifetime Lifetime in seconds
1025 * @param integer $maxFileSize Maximum filesize in bytes (default 5MB)
1026 * @param string $successRedirect Redirect URL or 200 / 201 status code
1027 * @param array $amzHeaders Array of x-amz-meta-* headers
1028 * @param array $headers Array of request headers or content type as a string
1029 * @param boolean $flashVars Includes additional "Filename" variable posted by Flash
1030 * @return object
1031 */
1032 public static function getHttpUploadPostParams($bucket, $uriPrefix = '', $acl = self::ACL_PRIVATE, $lifetime = 3600,
1033 $maxFileSize = 5242880, $successRedirect = "201", $amzHeaders = array(), $headers = array(), $flashVars = false)
1034 {
1035 // Create policy object
1036 $policy = new stdClass;
1037 $policy->expiration = gmdate('Y-m-d\TH:i:s\Z', (time() + $lifetime));
1038 $policy->conditions = array();
1039 $obj = new stdClass; $obj->bucket = $bucket; array_push($policy->conditions, $obj);
1040 $obj = new stdClass; $obj->acl = $acl; array_push($policy->conditions, $obj);
1041
1042 $obj = new stdClass; // 200 for non-redirect uploads
1043 if (is_numeric($successRedirect) && in_array((int)$successRedirect, array(200, 201)))
1044 $obj->success_action_status = (string)$successRedirect;
1045 else // URL
1046 $obj->success_action_redirect = $successRedirect;
1047 array_push($policy->conditions, $obj);
1048
1049 if ($acl !== self::ACL_PUBLIC_READ)
1050 array_push($policy->conditions, array('eq', '$acl', $acl));
1051
1052 array_push($policy->conditions, array('starts-with', '$key', $uriPrefix));
1053 if ($flashVars) array_push($policy->conditions, array('starts-with', '$Filename', ''));
1054 foreach (array_keys($headers) as $headerKey)
1055 array_push($policy->conditions, array('starts-with', '$'.$headerKey, ''));
1056 foreach ($amzHeaders as $headerKey => $headerVal)
1057 {
1058 $obj = new stdClass;
1059 $obj->{$headerKey} = (string)$headerVal;
1060 array_push($policy->conditions, $obj);
1061 }
1062 array_push($policy->conditions, array('content-length-range', 0, $maxFileSize));
1063 $policy = base64_encode(str_replace('\/', '/', json_encode($policy)));
1064
1065 // Create parameters
1066 $params = new stdClass;
1067 $params->AWSAccessKeyId = self::$__accessKey;
1068 $params->key = $uriPrefix.'${filename}';
1069 $params->acl = $acl;
1070 $params->policy = $policy; unset($policy);
1071 $params->signature = self::__getHash($params->policy);
1072 if (is_numeric($successRedirect) && in_array((int)$successRedirect, array(200, 201)))
1073 $params->success_action_status = (string)$successRedirect;
1074 else
1075 $params->success_action_redirect = $successRedirect;
1076 foreach ($headers as $headerKey => $headerVal) $params->{$headerKey} = (string)$headerVal;
1077 foreach ($amzHeaders as $headerKey => $headerVal) $params->{$headerKey} = (string)$headerVal;
1078 return $params;
1079 }
1080
1081
1082 /**
1083 * Create a CloudFront distribution
1084 *
1085 * @param string $bucket Bucket name
1086 * @param boolean $enabled Enabled (true/false)
1087 * @param array $cnames Array containing CNAME aliases
1088 * @param string $comment Use the bucket name as the hostname
1089 * @param string $defaultRootObject Default root object
1090 * @param string $originAccessIdentity Origin access identity
1091 * @param array $trustedSigners Array of trusted signers
1092 * @return array | false
1093 */
1094 public static function createDistribution($bucket, $enabled = true, $cnames = array(), $comment = null, $defaultRootObject = null, $originAccessIdentity = null, $trustedSigners = array())
1095 {
1096 if (!extension_loaded('openssl'))
1097 {
1098 self::__triggerError(sprintf("mwpS3::createDistribution({$bucket}, ".(int)$enabled.", [], '$comment'): %s",
1099 "CloudFront functionality requires SSL"), __FILE__, __LINE__);
1100 return false;
1101 }
1102 $useSSL = self::$useSSL;
1103
1104 self::$useSSL = true; // CloudFront requires SSL
1105 $rest = new mwpS3Request('POST', '', '2010-11-01/distribution', 'cloudfront.amazonaws.com');
1106 $rest->data = self::__getCloudFrontDistributionConfigXML(
1107 $bucket.'.s3.amazonaws.com',
1108 $enabled,
1109 (string)$comment,
1110 (string)microtime(true),
1111 $cnames,
1112 $defaultRootObject,
1113 $originAccessIdentity,
1114 $trustedSigners
1115 );
1116
1117 $rest->size = strlen($rest->data);
1118 $rest->setHeader('Content-Type', 'application/xml');
1119 $rest = self::__getCloudFrontResponse($rest);
1120
1121 self::$useSSL = $useSSL;
1122
1123 if ($rest->error === false && $rest->code !== 201)
1124 $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1125 if ($rest->error !== false)
1126 {
1127 self::__triggerError(sprintf("mwpS3::createDistribution({$bucket}, ".(int)$enabled.", [], '$comment'): [%s] %s",
1128 $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
1129 return false;
1130 } elseif ($rest->body instanceof SimpleXMLElement)
1131 return self::__parseCloudFrontDistributionConfig($rest->body);
1132 return false;
1133 }
1134
1135
1136 /**
1137 * Get CloudFront distribution info
1138 *
1139 * @param string $distributionId Distribution ID from listDistributions()
1140 * @return array | false
1141 */
1142 public static function getDistribution($distributionId)
1143 {
1144 if (!extension_loaded('openssl'))
1145 {
1146 self::__triggerError(sprintf("mwpS3::getDistribution($distributionId): %s",
1147 "CloudFront functionality requires SSL"), __FILE__, __LINE__);
1148 return false;
1149 }
1150 $useSSL = self::$useSSL;
1151
1152 self::$useSSL = true; // CloudFront requires SSL
1153 $rest = new mwpS3Request('GET', '', '2010-11-01/distribution/'.$distributionId, 'cloudfront.amazonaws.com');
1154 $rest = self::__getCloudFrontResponse($rest);
1155
1156 self::$useSSL = $useSSL;
1157
1158 if ($rest->error === false && $rest->code !== 200)
1159 $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1160 if ($rest->error !== false)
1161 {
1162 self::__triggerError(sprintf("mwpS3::getDistribution($distributionId): [%s] %s",
1163 $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
1164 return false;
1165 }
1166 elseif ($rest->body instanceof SimpleXMLElement)
1167 {
1168 $dist = self::__parseCloudFrontDistributionConfig($rest->body);
1169 $dist['hash'] = $rest->headers['hash'];
1170 $dist['id'] = $distributionId;
1171 return $dist;
1172 }
1173 return false;
1174 }
1175
1176
1177 /**
1178 * Update a CloudFront distribution
1179 *
1180 * @param array $dist Distribution array info identical to output of getDistribution()
1181 * @return array | false
1182 */
1183 public static function updateDistribution($dist)
1184 {
1185 if (!extension_loaded('openssl'))
1186 {
1187 self::__triggerError(sprintf("mwpS3::updateDistribution({$dist['id']}): %s",
1188 "CloudFront functionality requires SSL"), __FILE__, __LINE__);
1189 return false;
1190 }
1191
1192 $useSSL = self::$useSSL;
1193
1194 self::$useSSL = true; // CloudFront requires SSL
1195 $rest = new mwpS3Request('PUT', '', '2010-11-01/distribution/'.$dist['id'].'/config', 'cloudfront.amazonaws.com');
1196 $rest->data = self::__getCloudFrontDistributionConfigXML(
1197 $dist['origin'],
1198 $dist['enabled'],
1199 $dist['comment'],
1200 $dist['callerReference'],
1201 $dist['cnames'],
1202 $dist['defaultRootObject'],
1203 $dist['originAccessIdentity'],
1204 $dist['trustedSigners']
1205 );
1206
1207 $rest->size = strlen($rest->data);
1208 $rest->setHeader('If-Match', $dist['hash']);
1209 $rest = self::__getCloudFrontResponse($rest);
1210
1211 self::$useSSL = $useSSL;
1212
1213 if ($rest->error === false && $rest->code !== 200)
1214 $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1215 if ($rest->error !== false)
1216 {
1217 self::__triggerError(sprintf("mwpS3::updateDistribution({$dist['id']}): [%s] %s",
1218 $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
1219 return false;
1220 } else {
1221 $dist = self::__parseCloudFrontDistributionConfig($rest->body);
1222 $dist['hash'] = $rest->headers['hash'];
1223 return $dist;
1224 }
1225 return false;
1226 }
1227
1228
1229 /**
1230 * Delete a CloudFront distribution
1231 *
1232 * @param array $dist Distribution array info identical to output of getDistribution()
1233 * @return boolean
1234 */
1235 public static function deleteDistribution($dist)
1236 {
1237 if (!extension_loaded('openssl'))
1238 {
1239 self::__triggerError(sprintf("mwpS3::deleteDistribution({$dist['id']}): %s",
1240 "CloudFront functionality requires SSL"), __FILE__, __LINE__);
1241 return false;
1242 }
1243
1244 $useSSL = self::$useSSL;
1245
1246 self::$useSSL = true; // CloudFront requires SSL
1247 $rest = new mwpS3Request('DELETE', '', '2008-06-30/distribution/'.$dist['id'], 'cloudfront.amazonaws.com');
1248 $rest->setHeader('If-Match', $dist['hash']);
1249 $rest = self::__getCloudFrontResponse($rest);
1250
1251 self::$useSSL = $useSSL;
1252
1253 if ($rest->error === false && $rest->code !== 204)
1254 $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1255 if ($rest->error !== false)
1256 {
1257 self::__triggerError(sprintf("mwpS3::deleteDistribution({$dist['id']}): [%s] %s",
1258 $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
1259 return false;
1260 }
1261 return true;
1262 }
1263
1264
1265 /**
1266 * Get a list of CloudFront distributions
1267 *
1268 * @return array
1269 */
1270 public static function listDistributions()
1271 {
1272 if (!extension_loaded('openssl'))
1273 {
1274 self::__triggerError(sprintf("mwpS3::listDistributions(): [%s] %s",
1275 "CloudFront functionality requires SSL"), __FILE__, __LINE__);
1276 return false;
1277 }
1278
1279 $useSSL = self::$useSSL;
1280 self::$useSSL = true; // CloudFront requires SSL
1281 $rest = new mwpS3Request('GET', '', '2010-11-01/distribution', 'cloudfront.amazonaws.com');
1282 $rest = self::__getCloudFrontResponse($rest);
1283 self::$useSSL = $useSSL;
1284
1285 if ($rest->error === false && $rest->code !== 200)
1286 $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1287 if ($rest->error !== false)
1288 {
1289 self::__triggerError(sprintf("mwpS3::listDistributions(): [%s] %s",
1290 $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
1291 return false;
1292 }
1293 elseif ($rest->body instanceof SimpleXMLElement && isset($rest->body->DistributionSummary))
1294 {
1295 $list = array();
1296 if (isset($rest->body->Marker, $rest->body->MaxItems, $rest->body->IsTruncated))
1297 {
1298 //$info['marker'] = (string)$rest->body->Marker;
1299 //$info['maxItems'] = (int)$rest->body->MaxItems;
1300 //$info['isTruncated'] = (string)$rest->body->IsTruncated == 'true' ? true : false;
1301 }
1302 foreach ($rest->body->DistributionSummary as $summary)
1303 $list[(string)$summary->Id] = self::__parseCloudFrontDistributionConfig($summary);
1304
1305 return $list;
1306 }
1307 return array();
1308 }
1309
1310 /**
1311 * List CloudFront Origin Access Identities
1312 *
1313 * @return array
1314 */
1315 public static function listOriginAccessIdentities()
1316 {
1317 if (!extension_loaded('openssl'))
1318 {
1319 self::__triggerError(sprintf("mwpS3::listOriginAccessIdentities(): [%s] %s",
1320 "CloudFront functionality requires SSL"), __FILE__, __LINE__);
1321 return false;
1322 }
1323
1324 self::$useSSL = true; // CloudFront requires SSL
1325 $rest = new mwpS3Request('GET', '', '2010-11-01/origin-access-identity/cloudfront', 'cloudfront.amazonaws.com');
1326 $rest = self::__getCloudFrontResponse($rest);
1327 $useSSL = self::$useSSL;
1328
1329 if ($rest->error === false && $rest->code !== 200)
1330 $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1331 if ($rest->error !== false)
1332 {
1333 trigger_error(sprintf("mwpS3::listOriginAccessIdentities(): [%s] %s",
1334 $rest->error['code'], $rest->error['message']), E_USER_WARNING);
1335 return false;
1336 }
1337
1338 if (isset($rest->body->CloudFrontOriginAccessIdentitySummary))
1339 {
1340 $identities = array();
1341 foreach ($rest->body->CloudFrontOriginAccessIdentitySummary as $identity)
1342 if (isset($identity->S3CanonicalUserId))
1343 $identities[(string)$identity->Id] = array('id' => (string)$identity->Id, 's3CanonicalUserId' => (string)$identity->S3CanonicalUserId);
1344 return $identities;
1345 }
1346 return false;
1347 }
1348
1349
1350 /**
1351 * Invalidate objects in a CloudFront distribution
1352 *
1353 * Thanks to Martin Lindkvist for mwpS3::invalidateDistribution()
1354 *
1355 * @param string $distributionId Distribution ID from listDistributions()
1356 * @param array $paths Array of object paths to invalidate
1357 * @return boolean
1358 */
1359 public static function invalidateDistribution($distributionId, $paths)
1360 {
1361 if (!extension_loaded('openssl'))
1362 {
1363 self::__triggerError(sprintf("mwpS3::invalidateDistribution(): [%s] %s",
1364 "CloudFront functionality requires SSL"), __FILE__, __LINE__);
1365 return false;
1366 }
1367
1368 $useSSL = self::$useSSL;
1369 self::$useSSL = true; // CloudFront requires SSL
1370 $rest = new mwpS3Request('POST', '', '2010-08-01/distribution/'.$distributionId.'/invalidation', 'cloudfront.amazonaws.com');
1371 $rest->data = self::__getCloudFrontInvalidationBatchXML($paths, (string)microtime(true));
1372 $rest->size = strlen($rest->data);
1373 $rest = self::__getCloudFrontResponse($rest);
1374 self::$useSSL = $useSSL;
1375
1376 if ($rest->error === false && $rest->code !== 201)
1377 $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1378 if ($rest->error !== false)
1379 {
1380 trigger_error(sprintf("mwpS3::invalidate('{$distributionId}',{$paths}): [%s] %s",
1381 $rest->error['code'], $rest->error['message']), E_USER_WARNING);
1382 return false;
1383 }
1384 return true;
1385 }
1386
1387
1388 /**
1389 * Get a InvalidationBatch DOMDocument
1390 *
1391 * @internal Used to create XML in invalidateDistribution()
1392 * @param array $paths Paths to objects to invalidateDistribution
1393 * @return string
1394 */
1395 private static function __getCloudFrontInvalidationBatchXML($paths, $callerReference = '0') {
1396 $dom = new DOMDocument('1.0', 'UTF-8');
1397 $dom->formatOutput = true;
1398 $invalidationBatch = $dom->createElement('InvalidationBatch');
1399 foreach ($paths as $path)
1400 $invalidationBatch->appendChild($dom->createElement('Path', $path));
1401
1402 $invalidationBatch->appendChild($dom->createElement('CallerReference', $callerReference));
1403 $dom->appendChild($invalidationBatch);
1404 return $dom->saveXML();
1405 }
1406
1407
1408 /**
1409 * Get a DistributionConfig DOMDocument
1410 *
1411 * http://docs.amazonwebservices.com/AmazonCloudFront/latest/APIReference/index.html?PutConfig.html
1412 *
1413 * @internal Used to create XML in createDistribution() and updateDistribution()
1414 * @param string $bucket S3 Origin bucket
1415 * @param boolean $enabled Enabled (true/false)
1416 * @param string $comment Comment to append
1417 * @param string $callerReference Caller reference
1418 * @param array $cnames Array of CNAME aliases
1419 * @param string $defaultRootObject Default root object
1420 * @param string $originAccessIdentity Origin access identity
1421 * @param array $trustedSigners Array of trusted signers
1422 * @return string
1423 */
1424 private static function __getCloudFrontDistributionConfigXML($bucket, $enabled, $comment, $callerReference = '0', $cnames = array(), $defaultRootObject = null, $originAccessIdentity = null, $trustedSigners = array())
1425 {
1426 $dom = new DOMDocument('1.0', 'UTF-8');
1427 $dom->formatOutput = true;
1428 $distributionConfig = $dom->createElement('DistributionConfig');
1429 $distributionConfig->setAttribute('xmlns', 'http://cloudfront.amazonaws.com/doc/2010-11-01/');
1430
1431 $origin = $dom->createElement('S3Origin');
1432 $origin->appendChild($dom->createElement('DNSName', $bucket));
1433 if ($originAccessIdentity !== null) $origin->appendChild($dom->createElement('OriginAccessIdentity', $originAccessIdentity));
1434 $distributionConfig->appendChild($origin);
1435
1436 if ($defaultRootObject !== null) $distributionConfig->appendChild($dom->createElement('DefaultRootObject', $defaultRootObject));
1437
1438 $distributionConfig->appendChild($dom->createElement('CallerReference', $callerReference));
1439 foreach ($cnames as $cname)
1440 $distributionConfig->appendChild($dom->createElement('CNAME', $cname));
1441 if ($comment !== '') $distributionConfig->appendChild($dom->createElement('Comment', $comment));
1442 $distributionConfig->appendChild($dom->createElement('Enabled', $enabled ? 'true' : 'false'));
1443
1444 $trusted = $dom->createElement('TrustedSigners');
1445 foreach ($trustedSigners as $id => $type)
1446 $trusted->appendChild($id !== '' ? $dom->createElement($type, $id) : $dom->createElement($type));
1447 $distributionConfig->appendChild($trusted);
1448
1449 $dom->appendChild($distributionConfig);
1450 //var_dump($dom->saveXML());
1451 return $dom->saveXML();
1452 }
1453
1454
1455 /**
1456 * Parse a CloudFront distribution config
1457 *
1458 * See http://docs.amazonwebservices.com/AmazonCloudFront/latest/APIReference/index.html?GetDistribution.html
1459 *
1460 * @internal Used to parse the CloudFront DistributionConfig node to an array
1461 * @param object &$node DOMNode
1462 * @return array
1463 */
1464 private static function __parseCloudFrontDistributionConfig(&$node)
1465 {
1466 if (isset($node->DistributionConfig))
1467 return self::__parseCloudFrontDistributionConfig($node->DistributionConfig);
1468
1469 $dist = array();
1470 if (isset($node->Id, $node->Status, $node->LastModifiedTime, $node->DomainName))
1471 {
1472 $dist['id'] = (string)$node->Id;
1473 $dist['status'] = (string)$node->Status;
1474 $dist['time'] = strtotime((string)$node->LastModifiedTime);
1475 $dist['domain'] = (string)$node->DomainName;
1476 }
1477
1478 if (isset($node->CallerReference))
1479 $dist['callerReference'] = (string)$node->CallerReference;
1480
1481 if (isset($node->Enabled))
1482 $dist['enabled'] = (string)$node->Enabled == 'true' ? true : false;
1483
1484 if (isset($node->S3Origin))
1485 {
1486 if (isset($node->S3Origin->DNSName))
1487 $dist['origin'] = (string)$node->S3Origin->DNSName;
1488
1489 $dist['originAccessIdentity'] = isset($node->S3Origin->OriginAccessIdentity) ?
1490 (string)$node->S3Origin->OriginAccessIdentity : null;
1491 }
1492
1493 $dist['defaultRootObject'] = isset($node->DefaultRootObject) ? (string)$node->DefaultRootObject : null;
1494
1495 $dist['cnames'] = array();
1496 if (isset($node->CNAME))
1497 foreach ($node->CNAME as $cname)
1498 $dist['cnames'][(string)$cname] = (string)$cname;
1499
1500 $dist['trustedSigners'] = array();
1501 if (isset($node->TrustedSigners))
1502 foreach ($node->TrustedSigners as $signer)
1503 {
1504 if (isset($signer->Self))
1505 $dist['trustedSigners'][''] = 'Self';
1506 elseif (isset($signer->KeyPairId))
1507 $dist['trustedSigners'][(string)$signer->KeyPairId] = 'KeyPairId';
1508 elseif (isset($signer->AwsAccountNumber))
1509 $dist['trustedSigners'][(string)$signer->AwsAccountNumber] = 'AwsAccountNumber';
1510 }
1511
1512 $dist['comment'] = isset($node->Comment) ? (string)$node->Comment : null;
1513 return $dist;
1514 }
1515
1516
1517 /**
1518 * Grab CloudFront response
1519 *
1520 * @internal Used to parse the CloudFront S3Request::getResponse() output
1521 * @param object &$rest S3Request instance
1522 * @return object
1523 */
1524 private static function __getCloudFrontResponse(&$rest)
1525 {
1526 $rest->getResponse();
1527 if ($rest->response->error === false && isset($rest->response->body) &&
1528 is_string($rest->response->body) && substr($rest->response->body, 0, 5) == '<?xml')
1529 {
1530 $rest->response->body = simplexml_load_string($rest->response->body);
1531 // Grab CloudFront errors
1532 if (isset($rest->response->body->Error, $rest->response->body->Error->Code,
1533 $rest->response->body->Error->Message))
1534 {
1535 $rest->response->error = array(
1536 'code' => (string)$rest->response->body->Error->Code,
1537 'message' => (string)$rest->response->body->Error->Message
1538 );
1539 unset($rest->response->body);
1540 }
1541 }
1542 return $rest->response;
1543 }
1544
1545
1546 /**
1547 * Get MIME type for file
1548 *
1549 * @internal Used to get mime types
1550 * @param string &$file File path
1551 * @return string
1552 */
1553 public static function __getMimeType(&$file)
1554 {
1555 $type = false;
1556 // Fileinfo documentation says fileinfo_open() will use the
1557 // MAGIC env var for the magic file
1558 if (extension_loaded('fileinfo') && isset($_ENV['MAGIC']) &&
1559 ($finfo = finfo_open(FILEINFO_MIME, $_ENV['MAGIC'])) !== false)
1560 {
1561 if (($type = finfo_file($finfo, $file)) !== false)
1562 {
1563 // Remove the charset and grab the last content-type
1564 $type = explode(' ', str_replace('; charset=', ';charset=', $type));
1565 $type = array_pop($type);
1566 $type = explode(';', $type);
1567 $type = trim(array_shift($type));
1568 }
1569 finfo_close($finfo);
1570
1571 // If anyone is still using mime_content_type()
1572 } elseif (function_exists('mime_content_type'))
1573 $type = trim(mime_content_type($file));
1574
1575 if ($type !== false && strlen($type) > 0) return $type;
1576
1577 // Otherwise do it the old fashioned way
1578 static $exts = array(
1579 'jpg' => 'image/jpeg', 'gif' => 'image/gif', 'png' => 'image/png',
1580 'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'ico' => 'image/x-icon',
1581 'swf' => 'application/x-shockwave-flash', 'pdf' => 'application/pdf',
1582 'zip' => 'application/zip', 'gz' => 'application/x-gzip',
1583 'tar' => 'application/x-tar', 'bz' => 'application/x-bzip',
1584 'bz2' => 'application/x-bzip2', 'txt' => 'text/plain',
1585 'asc' => 'text/plain', 'htm' => 'text/html', 'html' => 'text/html',
1586 'css' => 'text/css', 'js' => 'text/javascript',
1587 'xml' => 'text/xml', 'xsl' => 'application/xsl+xml',
1588 'ogg' => 'application/ogg', 'mp3' => 'audio/mpeg', 'wav' => 'audio/x-wav',
1589 'avi' => 'video/x-msvideo', 'mpg' => 'video/mpeg', 'mpeg' => 'video/mpeg',
1590 'mov' => 'video/quicktime', 'flv' => 'video/x-flv', 'php' => 'text/x-php'
1591 );
1592 $ext = strtolower(pathInfo($file, PATHINFO_EXTENSION));
1593 return isset($exts[$ext]) ? $exts[$ext] : 'application/octet-stream';
1594 }
1595
1596
1597 /**
1598 * Generate the auth string: "AWS AccessKey:Signature"
1599 *
1600 * @internal Used by S3Request::getResponse()
1601 * @param string $string String to sign
1602 * @return string
1603 */
1604 public static function __getSignature($string)
1605 {
1606 return 'AWS '.self::$__accessKey.':'.self::__getHash($string);
1607 }
1608
1609
1610 /**
1611 * Creates a HMAC-SHA1 hash
1612 *
1613 * This uses the hash extension if loaded
1614 *
1615 * @internal Used by __getSignature()
1616 * @param string $string String to sign
1617 * @return string
1618 */
1619 private static function __getHash($string)
1620 {
1621 return base64_encode(extension_loaded('hash') ?
1622 hash_hmac('sha1', $string, self::$__secretKey, true) : pack('H*', sha1(
1623 (str_pad(self::$__secretKey, 64, chr(0x00)) ^ (str_repeat(chr(0x5c), 64))) .
1624 pack('H*', sha1((str_pad(self::$__secretKey, 64, chr(0x00)) ^
1625 (str_repeat(chr(0x36), 64))) . $string)))));
1626 }
1627
1628 }
1629
1630 final class mwpS3Request
1631 {
1632 private $endpoint, $verb, $bucket, $uri, $resource = '', $parameters = array(),
1633 $amzHeaders = array(), $headers = array(
1634 'Host' => '', 'Date' => '', 'Content-MD5' => '', 'Content-Type' => ''
1635 );
1636 public $fp = false, $size = 0, $data = false, $response;
1637
1638
1639 /**
1640 * Constructor
1641 *
1642 * @param string $verb Verb
1643 * @param string $bucket Bucket name
1644 * @param string $uri Object URI
1645 * @return mixed
1646 */
1647 function __construct($verb, $bucket = '', $uri = '', $endpoint = 's3.amazonaws.com')
1648 {
1649 $this->endpoint = $endpoint;
1650 $this->verb = $verb;
1651 $this->bucket = $bucket;
1652 $this->uri = $uri !== '' ? '/'.str_replace('%2F', '/', rawurlencode($uri)) : '/';
1653
1654 //if ($this->bucket !== '')
1655 // $this->resource = '/'.$this->bucket.$this->uri;
1656 //else
1657 // $this->resource = $this->uri;
1658
1659 if ($this->bucket !== '')
1660 {
1661 if ($this->__dnsBucketName($this->bucket))
1662 {
1663 $this->headers['Host'] = $this->bucket.'.'.$this->endpoint;
1664 $this->resource = '/'.$this->bucket.$this->uri;
1665 }
1666 else
1667 {
1668 $this->headers['Host'] = $this->endpoint;
1669 $this->uri = $this->uri;
1670 if ($this->bucket !== '') $this->uri = '/'.$this->bucket.$this->uri;
1671 $this->bucket = '';
1672 $this->resource = $this->uri;
1673 }
1674 }
1675 else
1676 {
1677 $this->headers['Host'] = $this->endpoint;
1678 $this->resource = $this->uri;
1679 }
1680
1681
1682 $this->headers['Date'] = gmdate('D, d M Y H:i:s T');
1683 $this->response = new STDClass;
1684 $this->response->error = false;
1685 }
1686
1687
1688 /**
1689 * Set request parameter
1690 *
1691 * @param string $key Key
1692 * @param string $value Value
1693 * @return void
1694 */
1695 public function setParameter($key, $value)
1696 {
1697 $this->parameters[$key] = $value;
1698 }
1699
1700
1701 /**
1702 * Set request header
1703 *
1704 * @param string $key Key
1705 * @param string $value Value
1706 * @return void
1707 */
1708 public function setHeader($key, $value)
1709 {
1710 $this->headers[$key] = $value;
1711 }
1712
1713
1714 /**
1715 * Set x-amz-meta-* header
1716 *
1717 * @param string $key Key
1718 * @param string $value Value
1719 * @return void
1720 */
1721 public function setAmzHeader($key, $value)
1722 {
1723 $this->amzHeaders[$key] = $value;
1724 }
1725
1726
1727 /**
1728 * Get the S3 response
1729 *
1730 * @return object | false
1731 */
1732 public function getResponse()
1733 {
1734 $query = '';
1735 if (sizeof($this->parameters) > 0)
1736 {
1737 $query = substr($this->uri, -1) !== '?' ? '?' : '&';
1738 foreach ($this->parameters as $var => $value)
1739 if ($value == null || $value == '') $query .= $var.'&';
1740 // Parameters should be encoded (thanks Sean O'Dea)
1741 else $query .= $var.'='.rawurlencode($value).'&';
1742 $query = substr($query, 0, -1);
1743 $this->uri .= $query;
1744
1745 if (array_key_exists('acl', $this->parameters) ||
1746 array_key_exists('location', $this->parameters) ||
1747 array_key_exists('torrent', $this->parameters) ||
1748 array_key_exists('website', $this->parameters) ||
1749 array_key_exists('logging', $this->parameters))
1750 $this->resource .= $query;
1751 }
1752 $url = (mwpS3::$useSSL ? 'https://' : 'http://') . ($this->headers['Host'] !== '' ? $this->headers['Host'] : $this->endpoint) . $this->uri;
1753
1754 //var_dump('bucket: ' . $this->bucket, 'uri: ' . $this->uri, 'resource: ' . $this->resource, 'url: ' . $url);
1755
1756 // Basic setup
1757 $curl = curl_init();
1758 curl_setopt($curl, CURLOPT_USERAGENT, 'S3/php');
1759 curl_setopt($curl,CURLOPT_TIMEOUT,600);
1760 if (mwpS3::$useSSL)
1761 {
1762 // SSL Validation can now be optional for those with broken OpenSSL installations
1763 curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, mwpS3::$useSSLValidation ? 1 : 0);
1764 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, mwpS3::$useSSLValidation ? 1 : 0);
1765
1766 if (mwpS3::$sslKey !== null) curl_setopt($curl, CURLOPT_SSLKEY, mwpS3::$sslKey);
1767 if (mwpS3::$sslCert !== null) curl_setopt($curl, CURLOPT_SSLCERT, mwpS3::$sslCert);
1768 if (mwpS3::$sslCACert !== null) curl_setopt($curl, CURLOPT_CAINFO, mwpS3::$sslCACert);
1769 }
1770
1771 curl_setopt($curl, CURLOPT_URL, $url);
1772
1773 if (mwpS3::$proxy != null && isset(mwpS3::$proxy['host']))
1774 {
1775 curl_setopt($curl, CURLOPT_PROXY, mwpS3::$proxy['host']);
1776 curl_setopt($curl, CURLOPT_PROXYTYPE, mwpS3::$proxy['type']);
1777 if (isset(mwpS3::$proxy['user'], mwpS3::$proxy['pass']) && $proxy['user'] != null && $proxy['pass'] != null)
1778 curl_setopt($curl, CURLOPT_PROXYUSERPWD, sprintf('%s:%s', mwpS3::$proxy['user'], mwpS3::$proxy['pass']));
1779 }
1780
1781 // Headers
1782 $headers = array(); $amz = array();
1783 foreach ($this->amzHeaders as $header => $value)
1784 if (strlen($value) > 0) $headers[] = $header.': '.$value;
1785 foreach ($this->headers as $header => $value)
1786 if (strlen($value) > 0) $headers[] = $header.': '.$value;
1787
1788 // Collect AMZ headers for signature
1789 foreach ($this->amzHeaders as $header => $value)
1790 if (strlen($value) > 0) $amz[] = strtolower($header).':'.$value;
1791
1792 // AMZ headers must be sorted
1793 if (sizeof($amz) > 0)
1794 {
1795 sort($amz);
1796 $amz = "\n".implode("\n", $amz);
1797 } else $amz = '';
1798
1799 if (mwpS3::hasAuth())
1800 {
1801 // Authorization string (CloudFront stringToSign should only contain a date)
1802 if ($this->headers['Host'] == 'cloudfront.amazonaws.com')
1803 $headers[] = 'Authorization: ' . mwpS3::__getSignature($this->headers['Date']);
1804 else
1805 {
1806 $headers[] = 'Authorization: ' . mwpS3::__getSignature(
1807 $this->verb."\n".
1808 $this->headers['Content-MD5']."\n".
1809 $this->headers['Content-Type']."\n".
1810 $this->headers['Date'].$amz."\n".
1811 $this->resource
1812 );
1813 }
1814 }
1815
1816 curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
1817 curl_setopt($curl, CURLOPT_HEADER, false);
1818 curl_setopt($curl, CURLOPT_RETURNTRANSFER, false);
1819 curl_setopt($curl, CURLOPT_WRITEFUNCTION, array(&$this, '__responseWriteCallback'));
1820 curl_setopt($curl, CURLOPT_HEADERFUNCTION, array(&$this, '__responseHeaderCallback'));
1821 curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
1822
1823 // Request types
1824 switch ($this->verb)
1825 {
1826 case 'GET': break;
1827 case 'PUT': case 'POST': // POST only used for CloudFront
1828 if ($this->fp !== false)
1829 {
1830 curl_setopt($curl, CURLOPT_PUT, true);
1831 curl_setopt($curl, CURLOPT_INFILE, $this->fp);
1832 if ($this->size >= 0)
1833 curl_setopt($curl, CURLOPT_INFILESIZE, $this->size);
1834 }
1835 elseif ($this->data !== false)
1836 {
1837 curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb);
1838 curl_setopt($curl, CURLOPT_POSTFIELDS, $this->data);
1839 }
1840 else
1841 curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb);
1842 break;
1843 case 'HEAD':
1844 curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'HEAD');
1845 curl_setopt($curl, CURLOPT_NOBODY, true);
1846 break;
1847 case 'DELETE':
1848 curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE');
1849 break;
1850 default: break;
1851 }
1852
1853 // Execute, grab errors
1854 if (curl_exec($curl))
1855 $this->response->code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
1856 else
1857 $this->response->error = array(
1858 'code' => curl_errno($curl),
1859 'message' => curl_error($curl),
1860 'resource' => $this->resource
1861 );
1862
1863 @curl_close($curl);
1864
1865 // Parse body into XML
1866 if ($this->response->error === false && isset($this->response->headers['type']) &&
1867 $this->response->headers['type'] == 'application/xml' && isset($this->response->body))
1868 {
1869 $this->response->body = simplexml_load_string($this->response->body);
1870
1871 // Grab S3 errors
1872 if (!in_array($this->response->code, array(200, 204, 206)) &&
1873 isset($this->response->body->Code, $this->response->body->Message))
1874 {
1875 $this->response->error = array(
1876 'code' => (string)$this->response->body->Code,
1877 'message' => (string)$this->response->body->Message
1878 );
1879 if (isset($this->response->body->Resource))
1880 $this->response->error['resource'] = (string)$this->response->body->Resource;
1881 unset($this->response->body);
1882 }
1883 }
1884
1885 // Clean up file resources
1886 if ($this->fp !== false && is_resource($this->fp)) fclose($this->fp);
1887
1888 return $this->response;
1889 }
1890
1891
1892 /**
1893 * CURL write callback
1894 *
1895 * @param resource &$curl CURL resource
1896 * @param string &$data Data
1897 * @return integer
1898 */
1899 private function __responseWriteCallback(&$curl, &$data)
1900 {
1901 if (in_array($this->response->code, array(200, 206)) && $this->fp !== false)
1902 return fwrite($this->fp, $data);
1903 else
1904 $this->response->body .= $data;
1905 return strlen($data);
1906 }
1907
1908
1909 /**
1910 * Check DNS conformity
1911 *
1912 * @param string $bucket Bucket name
1913 * @return boolean
1914 */
1915 private function __dnsBucketName($bucket)
1916 {
1917 if (strlen($bucket) > 63 || !preg_match("/[^a-z0-9\.-]/", $bucket)) return false;
1918 if (strstr($bucket, '-.') !== false) return false;
1919 if (strstr($bucket, '..') !== false) return false;
1920 if (!preg_match("/^[0-9a-z]/", $bucket)) return false;
1921 if (!preg_match("/[0-9a-z]$/", $bucket)) return false;
1922 return true;
1923 }
1924
1925
1926 /**
1927 * CURL header callback
1928 *
1929 * @param resource &$curl CURL resource
1930 * @param string &$data Data
1931 * @return integer
1932 */
1933 private function __responseHeaderCallback(&$curl, &$data)
1934 {
1935 if (($strlen = strlen($data)) <= 2) return $strlen;
1936 if (substr($data, 0, 4) == 'HTTP')
1937 $this->response->code = (int)substr($data, 9, 3);
1938 else
1939 {
1940 $data = trim($data);
1941 if (strpos($data, ': ') === false) return $strlen;
1942 list($header, $value) = explode(': ', $data, 2);
1943 if ($header == 'Last-Modified')
1944 $this->response->headers['time'] = strtotime($value);
1945 elseif ($header == 'Content-Length')
1946 $this->response->headers['size'] = (int)$value;
1947 elseif ($header == 'Content-Type')
1948 $this->response->headers['type'] = $value;
1949 elseif ($header == 'ETag')
1950 $this->response->headers['hash'] = $value{0} == '"' ? substr($value, 1, -1) : $value;
1951 elseif (preg_match('/^x-amz-meta-.*$/', $header))
1952 $this->response->headers[$header] = is_numeric($value) ? (int)$value : $value;
1953 }
1954 return $strlen;
1955 }
1956
1957 }
1958
1959 class mwpS3Exception extends Exception {
1960 function __construct($message, $file, $line, $code = 0)
1961 {
1962 parent::__construct($message, $code);
1963 $this->file = $file;
1964 $this->line = $line;
1965 }
1966 }
1967
1968