├── .gitignore
├── composer.json
├── example-form.php
├── README.md
├── example-cloudfront.php
├── example.php
├── example-wrapper.php
└── S3.php
/.gitignore:
--------------------------------------------------------------------------------
1 | vendor/
2 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tpyo/amazon-s3-php-class",
3 | "description": "A standalone Amazon S3 (REST) client for PHP 5.2.x using CURL that does not require PEAR.",
4 | "type": "library",
5 | "homepage": "https://github.com/tpyo/amazon-s3-php-class",
6 | "license": "BSD-2-Clause",
7 | "authors": [
8 | {
9 | "name": "Donovan Schönknecht",
10 | "email": "don@tpyo.net"
11 | }
12 | ],
13 | "require": {
14 | "php": ">=5.2.0"
15 | },
16 | "autoload": {
17 | "classmap": ["S3.php"]
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/example-form.php:
--------------------------------------------------------------------------------
1 | 123);
33 | $requestHeaders = array(
34 | 'Content-Type' => 'application/octet-stream',
35 | 'Content-Disposition' => 'attachment; filename=${filename}'
36 | );
37 |
38 | $params = S3::getHttpUploadPostParams(
39 | $bucket,
40 | $path,
41 | S3::ACL_PUBLIC_READ,
42 | $lifetime,
43 | $maxFileSize,
44 | 201, // Or a URL to redirect to on success
45 | $metaHeaders,
46 | $requestHeaders,
47 | false // False since we're not using flash
48 | );
49 |
50 | $uploadURL = 'https://' . $bucket . '.s3.amazonaws.com/';
51 |
52 | ?>
53 |
54 |
55 | S3 Form Upload
56 |
57 |
58 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Amazon S3 PHP Class
2 |
3 | ## Usage
4 |
5 | OO method (e,g; $s3->getObject(...)):
6 |
7 | ```php
8 | $s3 = new S3($awsAccessKey, $awsSecretKey);
9 | ```
10 |
11 | Statically (e,g; S3::getObject(...)):
12 |
13 | ```php
14 | S3::setAuth($awsAccessKey, $awsSecretKey);
15 | ```
16 |
17 |
18 | ### Object Operations
19 |
20 | #### Uploading objects
21 |
22 | Put an object from a file:
23 |
24 | ```php
25 | S3::putObject(S3::inputFile($file, false), $bucketName, $uploadName, S3::ACL_PUBLIC_READ)
26 | ```
27 |
28 | Put an object from a string and set its Content-Type:
29 |
30 | ```php
31 | S3::putObject($string, $bucketName, $uploadName, S3::ACL_PUBLIC_READ, array(), array('Content-Type' => 'text/plain'))
32 | ```
33 |
34 | Put an object from a resource (buffer/file size is required - note: the resource will be fclose()'d automatically):
35 |
36 | ```php
37 | S3::putObject(S3::inputResource(fopen($file, 'rb'), filesize($file)), $bucketName, $uploadName, S3::ACL_PUBLIC_READ)
38 | ```
39 |
40 | #### Retrieving objects
41 |
42 | Get an object:
43 |
44 | ```php
45 | S3::getObject($bucketName, $uploadName)
46 | ```
47 |
48 | Save an object to file:
49 |
50 | ```php
51 | S3::getObject($bucketName, $uploadName, $saveName)
52 | ```
53 |
54 | Save an object to a resource of any type:
55 |
56 | ```php
57 | S3::getObject($bucketName, $uploadName, fopen('savefile.txt', 'wb'))
58 | ```
59 |
60 | #### Copying and deleting objects
61 |
62 | Copy an object:
63 |
64 | ```php
65 | S3::copyObject($srcBucket, $srcName, $bucketName, $saveName, $metaHeaders = array(), $requestHeaders = array())
66 | ```
67 |
68 | Delete an object:
69 |
70 | ```php
71 | S3::deleteObject($bucketName, $uploadName)
72 | ```
73 |
74 | ### Bucket Operations
75 |
76 | Get a list of buckets:
77 |
78 | ```php
79 | S3::listBuckets() // Simple bucket list
80 | S3::listBuckets(true) // Detailed bucket list
81 | ```
82 |
83 | Create a bucket:
84 |
85 | ```php
86 | S3::putBucket($bucketName)
87 | ```
88 |
89 | Get the contents of a bucket:
90 |
91 | ```php
92 | S3::getBucket($bucketName)
93 | ```
94 |
95 | Delete an empty bucket:
96 |
97 | ```php
98 | S3::deleteBucket($bucketName)
99 | ```
100 |
101 |
--------------------------------------------------------------------------------
/example-cloudfront.php:
--------------------------------------------------------------------------------
1 | #!/usr/local/bin/php
2 | listBuckets(), 1)."\n";
40 |
41 |
42 | // Create a bucket with public read access
43 | if ($s3->putBucket($bucketName, S3::ACL_PUBLIC_READ)) {
44 | echo "Created bucket {$bucketName}".PHP_EOL;
45 |
46 | // Put our file (also with public read access)
47 | if ($s3->putObjectFile($uploadFile, $bucketName, baseName($uploadFile), S3::ACL_PUBLIC_READ)) {
48 | echo "S3::putObjectFile(): File copied to {$bucketName}/".baseName($uploadFile).PHP_EOL;
49 |
50 |
51 | // Get the contents of our bucket
52 | $contents = $s3->getBucket($bucketName);
53 | echo "S3::getBucket(): Files in bucket {$bucketName}: ".print_r($contents, 1);
54 |
55 |
56 | // Get object info
57 | $info = $s3->getObjectInfo($bucketName, baseName($uploadFile));
58 | echo "S3::getObjectInfo(): Info for {$bucketName}/".baseName($uploadFile).': '.print_r($info, 1);
59 |
60 |
61 | // You can also fetch the object into memory
62 | // var_dump("S3::getObject() to memory", $s3->getObject($bucketName, baseName($uploadFile)));
63 |
64 | // Or save it into a file (write stream)
65 | // var_dump("S3::getObject() to savefile.txt", $s3->getObject($bucketName, baseName($uploadFile), 'savefile.txt'));
66 |
67 | // Or write it to a resource (write stream)
68 | // var_dump("S3::getObject() to resource", $s3->getObject($bucketName, baseName($uploadFile), fopen('savefile.txt', 'wb')));
69 |
70 |
71 |
72 | // Get the access control policy for a bucket:
73 | // $acp = $s3->getAccessControlPolicy($bucketName);
74 | // echo "S3::getAccessControlPolicy(): {$bucketName}: ".print_r($acp, 1);
75 |
76 | // Update an access control policy ($acp should be the same as the data returned by S3::getAccessControlPolicy())
77 | // $s3->setAccessControlPolicy($bucketName, '', $acp);
78 | // $acp = $s3->getAccessControlPolicy($bucketName);
79 | // echo "S3::getAccessControlPolicy(): {$bucketName}: ".print_r($acp, 1);
80 |
81 |
82 | // Enable logging for a bucket:
83 | // $s3->setBucketLogging($bucketName, 'logbucket', 'prefix');
84 |
85 | // if (($logging = $s3->getBucketLogging($bucketName)) !== false) {
86 | // echo "S3::getBucketLogging(): Logging for {$bucketName}: ".print_r($contents, 1);
87 | // } else {
88 | // echo "S3::getBucketLogging(): Logging for {$bucketName} not enabled\n";
89 | // }
90 |
91 | // Disable bucket logging:
92 | // var_dump($s3->disableBucketLogging($bucketName));
93 |
94 |
95 | // Delete our file
96 | if ($s3->deleteObject($bucketName, baseName($uploadFile))) {
97 | echo "S3::deleteObject(): Deleted file\n";
98 |
99 | // Delete the bucket we created (a bucket has to be empty to be deleted)
100 | if ($s3->deleteBucket($bucketName)) {
101 | echo "S3::deleteBucket(): Deleted bucket {$bucketName}\n";
102 | } else {
103 | echo "S3::deleteBucket(): Failed to delete bucket (it probably isn't empty)\n";
104 | }
105 |
106 | } else {
107 | echo "S3::deleteObject(): Failed to delete file\n";
108 | }
109 | } else {
110 | echo "S3::putObjectFile(): Failed to copy file\n";
111 | }
112 | } else {
113 | echo "S3::putBucket(): Unable to create bucket (it may already exist and/or be owned by someone else)\n";
114 | }
115 |
--------------------------------------------------------------------------------
/example-wrapper.php:
--------------------------------------------------------------------------------
1 | #!/usr/local/bin/php
2 | url['host'], $this->url['path'])) !== false) ?
34 | array('size' => $info['size'], 'mtime' => $info['time'], 'ctime' => $info['time']) : false;
35 | }
36 |
37 | public function unlink($path) {
38 | self::__getURL($path);
39 | return self::deleteObject($this->url['host'], $this->url['path']);
40 | }
41 |
42 | public function mkdir($path, $mode, $options) {
43 | self::__getURL($path);
44 | return self::putBucket($this->url['host'], self::__translateMode($mode));
45 | }
46 |
47 | public function rmdir($path) {
48 | self::__getURL($path);
49 | return self::deleteBucket($this->url['host']);
50 | }
51 |
52 | public function dir_opendir($path, $options) {
53 | self::__getURL($path);
54 | if (($contents = self::getBucket($this->url['host'], $this->url['path'])) !== false) {
55 | $pathlen = strlen($this->url['path']);
56 | if (substr($this->url['path'], -1) == '/') $pathlen++;
57 | $this->buffer = array();
58 | foreach ($contents as $file) {
59 | if ($pathlen > 0) $file['name'] = substr($file['name'], $pathlen);
60 | $this->buffer[] = $file;
61 | }
62 | return true;
63 | }
64 | return false;
65 | }
66 |
67 | public function dir_readdir() {
68 | return (isset($this->buffer[$this->position])) ? $this->buffer[$this->position++]['name'] : false;
69 | }
70 |
71 | public function dir_rewinddir() {
72 | $this->position = 0;
73 | }
74 |
75 | public function dir_closedir() {
76 | $this->position = 0;
77 | unset($this->buffer);
78 | }
79 |
80 | public function stream_close() {
81 | if ($this->mode == 'w') {
82 | self::putObject($this->buffer, $this->url['host'], $this->url['path']);
83 | }
84 | $this->position = 0;
85 | unset($this->buffer);
86 | }
87 |
88 | public function stream_stat() {
89 | if (is_object($this->buffer) && isset($this->buffer->headers))
90 | return array(
91 | 'size' => $this->buffer->headers['size'],
92 | 'mtime' => $this->buffer->headers['time'],
93 | 'ctime' => $this->buffer->headers['time']
94 | );
95 | elseif (($info = self::getObjectInfo($this->url['host'], $this->url['path'])) !== false)
96 | return array('size' => $info['size'], 'mtime' => $info['time'], 'ctime' => $info['time']);
97 | return false;
98 | }
99 |
100 | public function stream_flush() {
101 | $this->position = 0;
102 | return true;
103 | }
104 |
105 | public function stream_open($path, $mode, $options, &$opened_path) {
106 | if (!in_array($mode, array('r', 'rb', 'w', 'wb'))) return false; // Mode not supported
107 | $this->mode = substr($mode, 0, 1);
108 | self::__getURL($path);
109 | $this->position = 0;
110 | if ($this->mode == 'r') {
111 | if (($this->buffer = self::getObject($this->url['host'], $this->url['path'])) !== false) {
112 | if (is_object($this->buffer->body)) $this->buffer->body = (string)$this->buffer->body;
113 | } else return false;
114 | }
115 | return true;
116 | }
117 |
118 | public function stream_read($count) {
119 | if ($this->mode !== 'r' && $this->buffer !== false) return false;
120 | $data = substr(is_object($this->buffer) ? $this->buffer->body : $this->buffer, $this->position, $count);
121 | $this->position += strlen($data);
122 | return $data;
123 | }
124 |
125 | public function stream_write($data) {
126 | if ($this->mode !== 'w') return 0;
127 | $left = substr($this->buffer, 0, $this->position);
128 | $right = substr($this->buffer, $this->position + strlen($data));
129 | $this->buffer = $left . $data . $right;
130 | $this->position += strlen($data);
131 | return strlen($data);
132 | }
133 |
134 | public function stream_tell() {
135 | return $this->position;
136 | }
137 |
138 | public function stream_eof() {
139 | return $this->position >= strlen(is_object($this->buffer) ? $this->buffer->body : $this->buffer);
140 | }
141 |
142 | public function stream_seek($offset, $whence) {
143 | switch ($whence) {
144 | case SEEK_SET:
145 | if ($offset < strlen($this->buffer->body) && $offset >= 0) {
146 | $this->position = $offset;
147 | return true;
148 | } else return false;
149 | break;
150 | case SEEK_CUR:
151 | if ($offset >= 0) {
152 | $this->position += $offset;
153 | return true;
154 | } else return false;
155 | break;
156 | case SEEK_END:
157 | $bytes = strlen($this->buffer->body);
158 | if ($bytes + $offset >= 0) {
159 | $this->position = $bytes + $offset;
160 | return true;
161 | } else return false;
162 | break;
163 | default: return false;
164 | }
165 | }
166 |
167 | private function __getURL($path) {
168 | $this->url = parse_url($path);
169 | if (!isset($this->url['scheme']) || $this->url['scheme'] !== 's3') return $this->url;
170 | if (isset($this->url['user'], $this->url['pass'])) self::setAuth($this->url['user'], $this->url['pass']);
171 | $this->url['path'] = isset($this->url['path']) ? substr($this->url['path'], 1) : '';
172 | }
173 |
174 | private function __translateMode($mode) {
175 | $acl = self::ACL_PRIVATE;
176 | if (($mode & 0x0020) || ($mode & 0x0004))
177 | $acl = self::ACL_PUBLIC_READ;
178 | // You probably don't want to enable public write access
179 | if (($mode & 0x0010) || ($mode & 0x0008) || ($mode & 0x0002) || ($mode & 0x0001))
180 | $acl = self::ACL_PUBLIC_READ; //$acl = self::ACL_PUBLIC_READ_WRITE;
181 | return $acl;
182 | }
183 | } stream_wrapper_register('s3', 'S3Wrapper');
184 |
185 |
186 | ################################################################################
187 |
188 |
189 | S3::setAuth(awsAccessKey, awsSecretKey);
190 |
191 |
192 | $bucketName = uniqid('s3test');
193 |
194 | echo "Creating bucket: {$bucketName}\n";
195 | var_dump(mkdir("s3://{$bucketName}"));
196 |
197 | echo "\nWriting file: {$bucketName}/test.txt\n";
198 | var_dump(file_put_contents("s3://{$bucketName}/test.txt", "Eureka!"));
199 |
200 | echo "\nReading file: {$bucketName}/test.txt\n";
201 | var_dump(file_get_contents("s3://{$bucketName}/test.txt"));
202 |
203 | echo "\nContents for bucket: {$bucketName}\n";
204 | foreach (new DirectoryIterator("s3://{$bucketName}") as $b) {
205 | echo "\t".$b."\n";
206 | }
207 |
208 | echo "\nUnlinking: {$bucketName}/test.txt\n";
209 | var_dump(unlink("s3://{$bucketName}/test.txt"));
210 |
211 | echo "\nRemoving bucket: {$bucketName}\n";
212 | var_dump(rmdir("s3://{$bucketName}"));
213 |
214 |
215 | #EOF
--------------------------------------------------------------------------------
/S3.php:
--------------------------------------------------------------------------------
1 | $host, 'type' => $type, 'user' => $user, 'pass' => $pass);
344 | }
345 |
346 |
347 | /**
348 | * Set the error mode to exceptions
349 | *
350 | * @param boolean $enabled Enable exceptions
351 | * @return void
352 | */
353 | public static function setExceptions($enabled = true)
354 | {
355 | self::$useExceptions = $enabled;
356 | }
357 |
358 |
359 | /**
360 | * Set AWS time correction offset (use carefully)
361 | *
362 | * This can be used when an inaccurate system time is generating
363 | * invalid request signatures. It should only be used as a last
364 | * resort when the system time cannot be changed.
365 | *
366 | * @param string $offset Time offset (set to zero to use AWS server time)
367 | * @return void
368 | */
369 | public static function setTimeCorrectionOffset($offset = 0)
370 | {
371 | if ($offset == 0)
372 | {
373 | $rest = new S3Request('HEAD');
374 | $rest = $rest->getResponse();
375 | $awstime = $rest->headers['date'];
376 | $systime = time();
377 | $offset = $systime > $awstime ? -($systime - $awstime) : ($awstime - $systime);
378 | }
379 | self::$__timeOffset = $offset;
380 | }
381 |
382 |
383 | /**
384 | * Set signing key
385 | *
386 | * @param string $keyPairId AWS Key Pair ID
387 | * @param string $signingKey Private Key
388 | * @param boolean $isFile Load private key from file, set to false to load string
389 | * @return boolean
390 | */
391 | public static function setSigningKey($keyPairId, $signingKey, $isFile = true)
392 | {
393 | self::$__signingKeyPairId = $keyPairId;
394 | if ((self::$__signingKeyResource = openssl_pkey_get_private($isFile ?
395 | file_get_contents($signingKey) : $signingKey)) !== false) return true;
396 | self::__triggerError('S3::setSigningKey(): Unable to open load private key: '.$signingKey, __FILE__, __LINE__);
397 | return false;
398 | }
399 |
400 |
401 |
402 | /**
403 | * Free signing key from memory, MUST be called if you are using setSigningKey()
404 | *
405 | * @return void
406 | */
407 | public static function freeSigningKey()
408 | {
409 | if (self::$__signingKeyResource !== false)
410 | openssl_free_key(self::$__signingKeyResource);
411 | }
412 |
413 | /**
414 | * Set progress function
415 | *
416 | * @param function $func Progress function
417 | * @return void
418 | */
419 | public static function setProgressFunction($func = null)
420 | {
421 | self::$progressFunction = $func;
422 | }
423 |
424 |
425 | /**
426 | * Internal error handler
427 | *
428 | * @internal Internal error handler
429 | * @param string $message Error message
430 | * @param string $file Filename
431 | * @param integer $line Line number
432 | * @param integer $code Error code
433 | * @return void
434 | */
435 | private static function __triggerError($message, $file, $line, $code = 0)
436 | {
437 | if (self::$useExceptions)
438 | throw new S3Exception($message, $file, $line, $code);
439 | else
440 | trigger_error($message, E_USER_WARNING);
441 | }
442 |
443 |
444 | /**
445 | * Get a list of buckets
446 | *
447 | * @param boolean $detailed Returns detailed bucket list when true
448 | * @return array | false
449 | */
450 | public static function listBuckets($detailed = false)
451 | {
452 | $rest = new S3Request('GET', '', '', self::$endpoint);
453 | $rest = $rest->getResponse();
454 | if ($rest->error === false && $rest->code !== 200)
455 | $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
456 | if ($rest->error !== false)
457 | {
458 | self::__triggerError(sprintf("S3::listBuckets(): [%s] %s", $rest->error['code'],
459 | $rest->error['message']), __FILE__, __LINE__);
460 | return false;
461 | }
462 | $results = array();
463 | if (!isset($rest->body->Buckets)) return $results;
464 |
465 | if ($detailed)
466 | {
467 | if (isset($rest->body->Owner, $rest->body->Owner->ID, $rest->body->Owner->DisplayName))
468 | $results['owner'] = array(
469 | 'id' => (string)$rest->body->Owner->ID, 'name' => (string)$rest->body->Owner->DisplayName
470 | );
471 | $results['buckets'] = array();
472 | foreach ($rest->body->Buckets->Bucket as $b)
473 | $results['buckets'][] = array(
474 | 'name' => (string)$b->Name, 'time' => strtotime((string)$b->CreationDate)
475 | );
476 | } else
477 | foreach ($rest->body->Buckets->Bucket as $b) $results[] = (string)$b->Name;
478 |
479 | return $results;
480 | }
481 |
482 |
483 | /**
484 | * Get contents for a bucket
485 | *
486 | * If maxKeys is null this method will loop through truncated result sets
487 | *
488 | * @param string $bucket Bucket name
489 | * @param string $prefix Prefix
490 | * @param string $marker Marker (last file listed)
491 | * @param string $maxKeys Max keys (maximum number of keys to return)
492 | * @param string $delimiter Delimiter
493 | * @param boolean $returnCommonPrefixes Set to true to return CommonPrefixes
494 | * @return array | false
495 | */
496 | public static function getBucket($bucket, $prefix = null, $marker = null, $maxKeys = null, $delimiter = null, $returnCommonPrefixes = false)
497 | {
498 | $rest = new S3Request('GET', $bucket, '', self::$endpoint);
499 | if ($maxKeys == 0) $maxKeys = null;
500 | if ($prefix !== null && $prefix !== '') $rest->setParameter('prefix', $prefix);
501 | if ($marker !== null && $marker !== '') $rest->setParameter('marker', $marker);
502 | if ($maxKeys !== null && $maxKeys !== '') $rest->setParameter('max-keys', $maxKeys);
503 | if ($delimiter !== null && $delimiter !== '') $rest->setParameter('delimiter', $delimiter);
504 | else if (!empty(self::$defDelimiter)) $rest->setParameter('delimiter', self::$defDelimiter);
505 | $response = $rest->getResponse();
506 | if ($response->error === false && $response->code !== 200)
507 | $response->error = array('code' => $response->code, 'message' => 'Unexpected HTTP status');
508 | if ($response->error !== false)
509 | {
510 | self::__triggerError(sprintf("S3::getBucket(): [%s] %s",
511 | $response->error['code'], $response->error['message']), __FILE__, __LINE__);
512 | return false;
513 | }
514 |
515 | $results = array();
516 |
517 | $nextMarker = null;
518 | if (isset($response->body, $response->body->Contents))
519 | foreach ($response->body->Contents as $c)
520 | {
521 | $results[(string)$c->Key] = array(
522 | 'name' => (string)$c->Key,
523 | 'time' => strtotime((string)$c->LastModified),
524 | 'size' => (int)$c->Size,
525 | 'hash' => substr((string)$c->ETag, 1, -1)
526 | );
527 | $nextMarker = (string)$c->Key;
528 | }
529 |
530 | if ($returnCommonPrefixes && isset($response->body, $response->body->CommonPrefixes))
531 | foreach ($response->body->CommonPrefixes as $c)
532 | $results[(string)$c->Prefix] = array('prefix' => (string)$c->Prefix);
533 |
534 | if (isset($response->body, $response->body->IsTruncated) &&
535 | (string)$response->body->IsTruncated == 'false') return $results;
536 |
537 | if (isset($response->body, $response->body->NextMarker))
538 | $nextMarker = (string)$response->body->NextMarker;
539 |
540 | // Loop through truncated results if maxKeys isn't specified
541 | if ($maxKeys == null && $nextMarker !== null && (string)$response->body->IsTruncated == 'true')
542 | do
543 | {
544 | $rest = new S3Request('GET', $bucket, '', self::$endpoint);
545 | if ($prefix !== null && $prefix !== '') $rest->setParameter('prefix', $prefix);
546 | $rest->setParameter('marker', $nextMarker);
547 | if ($delimiter !== null && $delimiter !== '') $rest->setParameter('delimiter', $delimiter);
548 |
549 | if (($response = $rest->getResponse()) == false || $response->code !== 200) break;
550 |
551 | if (isset($response->body, $response->body->Contents))
552 | foreach ($response->body->Contents as $c)
553 | {
554 | $results[(string)$c->Key] = array(
555 | 'name' => (string)$c->Key,
556 | 'time' => strtotime((string)$c->LastModified),
557 | 'size' => (int)$c->Size,
558 | 'hash' => substr((string)$c->ETag, 1, -1)
559 | );
560 | $nextMarker = (string)$c->Key;
561 | }
562 |
563 | if ($returnCommonPrefixes && isset($response->body, $response->body->CommonPrefixes))
564 | foreach ($response->body->CommonPrefixes as $c)
565 | $results[(string)$c->Prefix] = array('prefix' => (string)$c->Prefix);
566 |
567 | if (isset($response->body, $response->body->NextMarker))
568 | $nextMarker = (string)$response->body->NextMarker;
569 |
570 | } while ($response !== false && (string)$response->body->IsTruncated == 'true');
571 |
572 | return $results;
573 | }
574 |
575 |
576 | /**
577 | * Put a bucket
578 | *
579 | * @param string $bucket Bucket name
580 | * @param constant $acl ACL flag
581 | * @param string $location Set as "EU" to create buckets hosted in Europe
582 | * @return boolean
583 | */
584 | public static function putBucket($bucket, $acl = self::ACL_PRIVATE, $location = false)
585 | {
586 | $rest = new S3Request('PUT', $bucket, '', self::$endpoint);
587 | $rest->setAmzHeader('x-amz-acl', $acl);
588 |
589 | if ($location === false) $location = self::getRegion();
590 |
591 | if ($location !== false && $location !== "us-east-1")
592 | {
593 | $dom = new DOMDocument;
594 | $createBucketConfiguration = $dom->createElement('CreateBucketConfiguration');
595 | $locationConstraint = $dom->createElement('LocationConstraint', $location);
596 | $createBucketConfiguration->appendChild($locationConstraint);
597 | $dom->appendChild($createBucketConfiguration);
598 | $rest->data = $dom->saveXML();
599 | $rest->size = strlen($rest->data);
600 | $rest->setHeader('Content-Type', 'application/xml');
601 | }
602 | $rest = $rest->getResponse();
603 |
604 | if ($rest->error === false && $rest->code !== 200)
605 | $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
606 | if ($rest->error !== false)
607 | {
608 | self::__triggerError(sprintf("S3::putBucket({$bucket}, {$acl}, {$location}): [%s] %s",
609 | $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
610 | return false;
611 | }
612 | return true;
613 | }
614 |
615 |
616 | /**
617 | * Delete an empty bucket
618 | *
619 | * @param string $bucket Bucket name
620 | * @return boolean
621 | */
622 | public static function deleteBucket($bucket)
623 | {
624 | $rest = new S3Request('DELETE', $bucket, '', self::$endpoint);
625 | $rest = $rest->getResponse();
626 | if ($rest->error === false && $rest->code !== 204)
627 | $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
628 | if ($rest->error !== false)
629 | {
630 | self::__triggerError(sprintf("S3::deleteBucket({$bucket}): [%s] %s",
631 | $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
632 | return false;
633 | }
634 | return true;
635 | }
636 |
637 |
638 | /**
639 | * Create input info array for putObject()
640 | *
641 | * @param string $file Input file
642 | * @param mixed $md5sum Use MD5 hash (supply a string if you want to use your own)
643 | * @return array | false
644 | */
645 | public static function inputFile($file, $md5sum = true)
646 | {
647 | if (!file_exists($file) || !is_file($file) || !is_readable($file))
648 | {
649 | self::__triggerError('S3::inputFile(): Unable to open input file: '.$file, __FILE__, __LINE__);
650 | return false;
651 | }
652 | clearstatcache(false, $file);
653 | return array('file' => $file, 'size' => filesize($file), 'md5sum' => $md5sum !== false ?
654 | (is_string($md5sum) ? $md5sum : base64_encode(md5_file($file, true))) : '', 'sha256sum' => hash_file('sha256', $file));
655 | }
656 |
657 |
658 | /**
659 | * Create input array info for putObject() with a resource
660 | *
661 | * @param string $resource Input resource to read from
662 | * @param integer $bufferSize Input byte size
663 | * @param string $md5sum MD5 hash to send (optional)
664 | * @return array | false
665 | */
666 | public static function inputResource(&$resource, $bufferSize = false, $md5sum = '')
667 | {
668 | if (!is_resource($resource) || (int)$bufferSize < 0)
669 | {
670 | self::__triggerError('S3::inputResource(): Invalid resource or buffer size', __FILE__, __LINE__);
671 | return false;
672 | }
673 |
674 | // Try to figure out the bytesize
675 | if ($bufferSize === false)
676 | {
677 | if (fseek($resource, 0, SEEK_END) < 0 || ($bufferSize = ftell($resource)) === false)
678 | {
679 | self::__triggerError('S3::inputResource(): Unable to obtain resource size', __FILE__, __LINE__);
680 | return false;
681 | }
682 | fseek($resource, 0);
683 | }
684 |
685 | $input = array('size' => $bufferSize, 'md5sum' => $md5sum);
686 | $input['fp'] =& $resource;
687 | return $input;
688 | }
689 |
690 |
691 | /**
692 | * Put an object
693 | *
694 | * @param mixed $input Input data
695 | * @param string $bucket Bucket name
696 | * @param string $uri Object URI
697 | * @param constant $acl ACL constant
698 | * @param array $metaHeaders Array of x-amz-meta-* headers
699 | * @param array $requestHeaders Array of request headers or content type as a string
700 | * @param constant $storageClass Storage class constant
701 | * @param constant $serverSideEncryption Server-side encryption
702 | * @return boolean
703 | */
704 | public static function putObject($input, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD, $serverSideEncryption = self::SSE_NONE)
705 | {
706 | if ($input === false) return false;
707 | $rest = new S3Request('PUT', $bucket, $uri, self::$endpoint);
708 |
709 | if (!is_array($input)) $input = array(
710 | 'data' => $input, 'size' => strlen($input),
711 | 'md5sum' => base64_encode(md5($input, true)),
712 | 'sha256sum' => hash('sha256', $input)
713 | );
714 |
715 | // Data
716 | if (isset($input['fp']))
717 | $rest->fp =& $input['fp'];
718 | elseif (isset($input['file']))
719 | $rest->fp = @fopen($input['file'], 'rb');
720 | elseif (isset($input['data']))
721 | $rest->data = $input['data'];
722 |
723 | // Content-Length (required)
724 | if (isset($input['size']) && $input['size'] >= 0)
725 | $rest->size = $input['size'];
726 | else {
727 | if (isset($input['file'])) {
728 | clearstatcache(false, $input['file']);
729 | $rest->size = filesize($input['file']);
730 | }
731 | elseif (isset($input['data']))
732 | $rest->size = strlen($input['data']);
733 | }
734 |
735 | // Custom request headers (Content-Type, Content-Disposition, Content-Encoding)
736 | if (is_array($requestHeaders))
737 | foreach ($requestHeaders as $h => $v)
738 | strpos($h, 'x-amz-') === 0 ? $rest->setAmzHeader($h, $v) : $rest->setHeader($h, $v);
739 | elseif (is_string($requestHeaders)) // Support for legacy contentType parameter
740 | $input['type'] = $requestHeaders;
741 |
742 | // Content-Type
743 | if (!isset($input['type']))
744 | {
745 | if (isset($requestHeaders['Content-Type']))
746 | $input['type'] =& $requestHeaders['Content-Type'];
747 | elseif (isset($input['file']))
748 | $input['type'] = self::__getMIMEType($input['file']);
749 | else
750 | $input['type'] = 'application/octet-stream';
751 | }
752 |
753 | if ($storageClass !== self::STORAGE_CLASS_STANDARD) // Storage class
754 | $rest->setAmzHeader('x-amz-storage-class', $storageClass);
755 |
756 | if ($serverSideEncryption !== self::SSE_NONE) // Server-side encryption
757 | $rest->setAmzHeader('x-amz-server-side-encryption', $serverSideEncryption);
758 |
759 | // We need to post with Content-Length and Content-Type, MD5 is optional
760 | if ($rest->size >= 0 && ($rest->fp !== false || $rest->data !== false))
761 | {
762 | $rest->setHeader('Content-Type', $input['type']);
763 | if (isset($input['md5sum'])) $rest->setHeader('Content-MD5', $input['md5sum']);
764 |
765 | if (isset($input['sha256sum'])) $rest->setAmzHeader('x-amz-content-sha256', $input['sha256sum']);
766 |
767 | $rest->setAmzHeader('x-amz-acl', $acl);
768 | foreach ($metaHeaders as $h => $v) $rest->setAmzHeader('x-amz-meta-'.$h, $v);
769 | $rest->getResponse();
770 | } else
771 | $rest->response->error = array('code' => 0, 'message' => 'Missing input parameters');
772 |
773 | if ($rest->response->error === false && $rest->response->code !== 200)
774 | $rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status');
775 | if ($rest->response->error !== false)
776 | {
777 | self::__triggerError(sprintf("S3::putObject(): [%s] %s",
778 | $rest->response->error['code'], $rest->response->error['message']), __FILE__, __LINE__);
779 | return false;
780 | }
781 | return true;
782 | }
783 |
784 |
785 | /**
786 | * Put an object from a file (legacy function)
787 | *
788 | * @param string $file Input file path
789 | * @param string $bucket Bucket name
790 | * @param string $uri Object URI
791 | * @param constant $acl ACL constant
792 | * @param array $metaHeaders Array of x-amz-meta-* headers
793 | * @param string $contentType Content type
794 | * @return boolean
795 | */
796 | public static function putObjectFile($file, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = null)
797 | {
798 | return self::putObject(self::inputFile($file), $bucket, $uri, $acl, $metaHeaders, $contentType);
799 | }
800 |
801 |
802 | /**
803 | * Put an object from a string (legacy function)
804 | *
805 | * @param string $string Input data
806 | * @param string $bucket Bucket name
807 | * @param string $uri Object URI
808 | * @param constant $acl ACL constant
809 | * @param array $metaHeaders Array of x-amz-meta-* headers
810 | * @param string $contentType Content type
811 | * @return boolean
812 | */
813 | public static function putObjectString($string, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = 'text/plain')
814 | {
815 | return self::putObject($string, $bucket, $uri, $acl, $metaHeaders, $contentType);
816 | }
817 |
818 |
819 | /**
820 | * Get an object
821 | *
822 | * @param string $bucket Bucket name
823 | * @param string $uri Object URI
824 | * @param mixed $saveTo Filename or resource to write to
825 | * @return mixed
826 | */
827 | public static function getObject($bucket, $uri, $saveTo = false)
828 | {
829 | $rest = new S3Request('GET', $bucket, $uri, self::$endpoint);
830 | if ($saveTo !== false)
831 | {
832 | if (is_resource($saveTo))
833 | $rest->fp =& $saveTo;
834 | else
835 | if (($rest->fp = @fopen($saveTo, 'wb')) !== false)
836 | $rest->file = realpath($saveTo);
837 | else
838 | $rest->response->error = array('code' => 0, 'message' => 'Unable to open save file for writing: '.$saveTo);
839 | }
840 | if ($rest->response->error === false) $rest->getResponse();
841 |
842 | if ($rest->response->error === false && $rest->response->code !== 200)
843 | $rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status');
844 | if ($rest->response->error !== false)
845 | {
846 | self::__triggerError(sprintf("S3::getObject({$bucket}, {$uri}): [%s] %s",
847 | $rest->response->error['code'], $rest->response->error['message']), __FILE__, __LINE__);
848 | return false;
849 | }
850 | return $rest->response;
851 | }
852 |
853 |
854 | /**
855 | * Get object information
856 | *
857 | * @param string $bucket Bucket name
858 | * @param string $uri Object URI
859 | * @param boolean $returnInfo Return response information
860 | * @return mixed | false
861 | */
862 | public static function getObjectInfo($bucket, $uri, $returnInfo = true)
863 | {
864 | $rest = new S3Request('HEAD', $bucket, $uri, self::$endpoint);
865 | $rest = $rest->getResponse();
866 | if ($rest->error === false && ($rest->code !== 200 && $rest->code !== 404))
867 | $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
868 | if ($rest->error !== false)
869 | {
870 | self::__triggerError(sprintf("S3::getObjectInfo({$bucket}, {$uri}): [%s] %s",
871 | $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
872 | return false;
873 | }
874 | return $rest->code == 200 ? $returnInfo ? $rest->headers : true : false;
875 | }
876 |
877 |
878 | /**
879 | * Copy an object
880 | *
881 | * @param string $srcBucket Source bucket name
882 | * @param string $srcUri Source object URI
883 | * @param string $bucket Destination bucket name
884 | * @param string $uri Destination object URI
885 | * @param constant $acl ACL constant
886 | * @param array $metaHeaders Optional array of x-amz-meta-* headers
887 | * @param array $requestHeaders Optional array of request headers (content type, disposition, etc.)
888 | * @param constant $storageClass Storage class constant
889 | * @return mixed | false
890 | */
891 | public static function copyObject($srcBucket, $srcUri, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD)
892 | {
893 | $rest = new S3Request('PUT', $bucket, $uri, self::$endpoint);
894 | $rest->setHeader('Content-Length', 0);
895 | foreach ($requestHeaders as $h => $v)
896 | strpos($h, 'x-amz-') === 0 ? $rest->setAmzHeader($h, $v) : $rest->setHeader($h, $v);
897 | foreach ($metaHeaders as $h => $v) $rest->setAmzHeader('x-amz-meta-'.$h, $v);
898 | if ($storageClass !== self::STORAGE_CLASS_STANDARD) // Storage class
899 | $rest->setAmzHeader('x-amz-storage-class', $storageClass);
900 | $rest->setAmzHeader('x-amz-acl', $acl);
901 | $rest->setAmzHeader('x-amz-copy-source', sprintf('/%s/%s', $srcBucket, rawurlencode($srcUri)));
902 | if (sizeof($requestHeaders) > 0 || sizeof($metaHeaders) > 0)
903 | $rest->setAmzHeader('x-amz-metadata-directive', 'REPLACE');
904 |
905 | $rest = $rest->getResponse();
906 | if ($rest->error === false && $rest->code !== 200)
907 | $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
908 | if ($rest->error !== false)
909 | {
910 | self::__triggerError(sprintf("S3::copyObject({$srcBucket}, {$srcUri}, {$bucket}, {$uri}): [%s] %s",
911 | $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
912 | return false;
913 | }
914 | return isset($rest->body->LastModified, $rest->body->ETag) ? array(
915 | 'time' => strtotime((string)$rest->body->LastModified),
916 | 'hash' => substr((string)$rest->body->ETag, 1, -1)
917 | ) : false;
918 | }
919 |
920 |
921 | /**
922 | * Set up a bucket redirection
923 | *
924 | * @param string $bucket Bucket name
925 | * @param string $location Target host name
926 | * @return boolean
927 | */
928 | public static function setBucketRedirect($bucket = NULL, $location = NULL)
929 | {
930 | $rest = new S3Request('PUT', $bucket, '', self::$endpoint);
931 |
932 | if( empty($bucket) || empty($location) ) {
933 | self::__triggerError("S3::setBucketRedirect({$bucket}, {$location}): Empty parameter.", __FILE__, __LINE__);
934 | return false;
935 | }
936 |
937 | $dom = new DOMDocument;
938 | $websiteConfiguration = $dom->createElement('WebsiteConfiguration');
939 | $redirectAllRequestsTo = $dom->createElement('RedirectAllRequestsTo');
940 | $hostName = $dom->createElement('HostName', $location);
941 | $redirectAllRequestsTo->appendChild($hostName);
942 | $websiteConfiguration->appendChild($redirectAllRequestsTo);
943 | $dom->appendChild($websiteConfiguration);
944 | $rest->setParameter('website', null);
945 | $rest->data = $dom->saveXML();
946 | $rest->size = strlen($rest->data);
947 | $rest->setHeader('Content-Type', 'application/xml');
948 | $rest = $rest->getResponse();
949 |
950 | if ($rest->error === false && $rest->code !== 200)
951 | $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
952 | if ($rest->error !== false)
953 | {
954 | self::__triggerError(sprintf("S3::setBucketRedirect({$bucket}, {$location}): [%s] %s",
955 | $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
956 | return false;
957 | }
958 | return true;
959 | }
960 |
961 |
962 | /**
963 | * Set logging for a bucket
964 | *
965 | * @param string $bucket Bucket name
966 | * @param string $targetBucket Target bucket (where logs are stored)
967 | * @param string $targetPrefix Log prefix (e,g; domain.com-)
968 | * @return boolean
969 | */
970 | public static function setBucketLogging($bucket, $targetBucket, $targetPrefix = null)
971 | {
972 | // The S3 log delivery group has to be added to the target bucket's ACP
973 | if ($targetBucket !== null && ($acp = self::getAccessControlPolicy($targetBucket, '')) !== false)
974 | {
975 | // Only add permissions to the target bucket when they do not exist
976 | $aclWriteSet = false;
977 | $aclReadSet = false;
978 | foreach ($acp['acl'] as $acl)
979 | if ($acl['type'] == 'Group' && $acl['uri'] == 'http://acs.amazonaws.com/groups/s3/LogDelivery')
980 | {
981 | if ($acl['permission'] == 'WRITE') $aclWriteSet = true;
982 | elseif ($acl['permission'] == 'READ_ACP') $aclReadSet = true;
983 | }
984 | if (!$aclWriteSet) $acp['acl'][] = array(
985 | 'type' => 'Group', 'uri' => 'http://acs.amazonaws.com/groups/s3/LogDelivery', 'permission' => 'WRITE'
986 | );
987 | if (!$aclReadSet) $acp['acl'][] = array(
988 | 'type' => 'Group', 'uri' => 'http://acs.amazonaws.com/groups/s3/LogDelivery', 'permission' => 'READ_ACP'
989 | );
990 | if (!$aclReadSet || !$aclWriteSet) self::setAccessControlPolicy($targetBucket, '', $acp);
991 | }
992 |
993 | $dom = new DOMDocument;
994 | $bucketLoggingStatus = $dom->createElement('BucketLoggingStatus');
995 | $bucketLoggingStatus->setAttribute('xmlns', 'http://s3.amazonaws.com/doc/2006-03-01/');
996 | if ($targetBucket !== null)
997 | {
998 | if ($targetPrefix == null) $targetPrefix = $bucket . '-';
999 | $loggingEnabled = $dom->createElement('LoggingEnabled');
1000 | $loggingEnabled->appendChild($dom->createElement('TargetBucket', $targetBucket));
1001 | $loggingEnabled->appendChild($dom->createElement('TargetPrefix', $targetPrefix));
1002 | // TODO: Add TargetGrants?
1003 | $bucketLoggingStatus->appendChild($loggingEnabled);
1004 | }
1005 | $dom->appendChild($bucketLoggingStatus);
1006 |
1007 | $rest = new S3Request('PUT', $bucket, '', self::$endpoint);
1008 | $rest->setParameter('logging', null);
1009 | $rest->data = $dom->saveXML();
1010 | $rest->size = strlen($rest->data);
1011 | $rest->setHeader('Content-Type', 'application/xml');
1012 | $rest = $rest->getResponse();
1013 | if ($rest->error === false && $rest->code !== 200)
1014 | $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1015 | if ($rest->error !== false)
1016 | {
1017 | self::__triggerError(sprintf("S3::setBucketLogging({$bucket}, {$targetBucket}): [%s] %s",
1018 | $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
1019 | return false;
1020 | }
1021 | return true;
1022 | }
1023 |
1024 |
1025 | /**
1026 | * Get logging status for a bucket
1027 | *
1028 | * This will return false if logging is not enabled.
1029 | * Note: To enable logging, you also need to grant write access to the log group
1030 | *
1031 | * @param string $bucket Bucket name
1032 | * @return array | false
1033 | */
1034 | public static function getBucketLogging($bucket)
1035 | {
1036 | $rest = new S3Request('GET', $bucket, '', self::$endpoint);
1037 | $rest->setParameter('logging', null);
1038 | $rest = $rest->getResponse();
1039 | if ($rest->error === false && $rest->code !== 200)
1040 | $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1041 | if ($rest->error !== false)
1042 | {
1043 | self::__triggerError(sprintf("S3::getBucketLogging({$bucket}): [%s] %s",
1044 | $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
1045 | return false;
1046 | }
1047 | if (!isset($rest->body->LoggingEnabled)) return false; // No logging
1048 | return array(
1049 | 'targetBucket' => (string)$rest->body->LoggingEnabled->TargetBucket,
1050 | 'targetPrefix' => (string)$rest->body->LoggingEnabled->TargetPrefix,
1051 | );
1052 | }
1053 |
1054 |
1055 | /**
1056 | * Disable bucket logging
1057 | *
1058 | * @param string $bucket Bucket name
1059 | * @return boolean
1060 | */
1061 | public static function disableBucketLogging($bucket)
1062 | {
1063 | return self::setBucketLogging($bucket, null);
1064 | }
1065 |
1066 |
1067 | /**
1068 | * Get a bucket's location
1069 | *
1070 | * @param string $bucket Bucket name
1071 | * @return string | false
1072 | */
1073 | public static function getBucketLocation($bucket)
1074 | {
1075 | $rest = new S3Request('GET', $bucket, '', self::$endpoint);
1076 | $rest->setParameter('location', null);
1077 | $rest = $rest->getResponse();
1078 | if ($rest->error === false && $rest->code !== 200)
1079 | $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1080 | if ($rest->error !== false)
1081 | {
1082 | self::__triggerError(sprintf("S3::getBucketLocation({$bucket}): [%s] %s",
1083 | $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
1084 | return false;
1085 | }
1086 | return (isset($rest->body[0]) && (string)$rest->body[0] !== '') ? (string)$rest->body[0] : 'US';
1087 | }
1088 |
1089 |
1090 | /**
1091 | * Set object or bucket Access Control Policy
1092 | *
1093 | * @param string $bucket Bucket name
1094 | * @param string $uri Object URI
1095 | * @param array $acp Access Control Policy Data (same as the data returned from getAccessControlPolicy)
1096 | * @return boolean
1097 | */
1098 | public static function setAccessControlPolicy($bucket, $uri = '', $acp = array())
1099 | {
1100 | $dom = new DOMDocument;
1101 | $dom->formatOutput = true;
1102 | $accessControlPolicy = $dom->createElement('AccessControlPolicy');
1103 | $accessControlList = $dom->createElement('AccessControlList');
1104 |
1105 | // It seems the owner has to be passed along too
1106 | $owner = $dom->createElement('Owner');
1107 | $owner->appendChild($dom->createElement('ID', $acp['owner']['id']));
1108 | $owner->appendChild($dom->createElement('DisplayName', $acp['owner']['name']));
1109 | $accessControlPolicy->appendChild($owner);
1110 |
1111 | foreach ($acp['acl'] as $g)
1112 | {
1113 | $grant = $dom->createElement('Grant');
1114 | $grantee = $dom->createElement('Grantee');
1115 | $grantee->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
1116 | if (isset($g['id']))
1117 | { // CanonicalUser (DisplayName is omitted)
1118 | $grantee->setAttribute('xsi:type', 'CanonicalUser');
1119 | $grantee->appendChild($dom->createElement('ID', $g['id']));
1120 | }
1121 | elseif (isset($g['email']))
1122 | { // AmazonCustomerByEmail
1123 | $grantee->setAttribute('xsi:type', 'AmazonCustomerByEmail');
1124 | $grantee->appendChild($dom->createElement('EmailAddress', $g['email']));
1125 | }
1126 | elseif ($g['type'] == 'Group')
1127 | { // Group
1128 | $grantee->setAttribute('xsi:type', 'Group');
1129 | $grantee->appendChild($dom->createElement('URI', $g['uri']));
1130 | }
1131 | $grant->appendChild($grantee);
1132 | $grant->appendChild($dom->createElement('Permission', $g['permission']));
1133 | $accessControlList->appendChild($grant);
1134 | }
1135 |
1136 | $accessControlPolicy->appendChild($accessControlList);
1137 | $dom->appendChild($accessControlPolicy);
1138 |
1139 | $rest = new S3Request('PUT', $bucket, $uri, self::$endpoint);
1140 | $rest->setParameter('acl', null);
1141 | $rest->data = $dom->saveXML();
1142 | $rest->size = strlen($rest->data);
1143 | $rest->setHeader('Content-Type', 'application/xml');
1144 | $rest = $rest->getResponse();
1145 | if ($rest->error === false && $rest->code !== 200)
1146 | $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1147 | if ($rest->error !== false)
1148 | {
1149 | self::__triggerError(sprintf("S3::setAccessControlPolicy({$bucket}, {$uri}): [%s] %s",
1150 | $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
1151 | return false;
1152 | }
1153 | return true;
1154 | }
1155 |
1156 |
1157 | /**
1158 | * Get object or bucket Access Control Policy
1159 | *
1160 | * @param string $bucket Bucket name
1161 | * @param string $uri Object URI
1162 | * @return mixed | false
1163 | */
1164 | public static function getAccessControlPolicy($bucket, $uri = '')
1165 | {
1166 | $rest = new S3Request('GET', $bucket, $uri, self::$endpoint);
1167 | $rest->setParameter('acl', null);
1168 | $rest = $rest->getResponse();
1169 | if ($rest->error === false && $rest->code !== 200)
1170 | $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1171 | if ($rest->error !== false)
1172 | {
1173 | self::__triggerError(sprintf("S3::getAccessControlPolicy({$bucket}, {$uri}): [%s] %s",
1174 | $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
1175 | return false;
1176 | }
1177 |
1178 | $acp = array();
1179 | if (isset($rest->body->Owner, $rest->body->Owner->ID, $rest->body->Owner->DisplayName))
1180 | $acp['owner'] = array(
1181 | 'id' => (string)$rest->body->Owner->ID, 'name' => (string)$rest->body->Owner->DisplayName
1182 | );
1183 |
1184 | if (isset($rest->body->AccessControlList))
1185 | {
1186 | $acp['acl'] = array();
1187 | foreach ($rest->body->AccessControlList->Grant as $grant)
1188 | {
1189 | foreach ($grant->Grantee as $grantee)
1190 | {
1191 | if (isset($grantee->ID, $grantee->DisplayName)) // CanonicalUser
1192 | $acp['acl'][] = array(
1193 | 'type' => 'CanonicalUser',
1194 | 'id' => (string)$grantee->ID,
1195 | 'name' => (string)$grantee->DisplayName,
1196 | 'permission' => (string)$grant->Permission
1197 | );
1198 | elseif (isset($grantee->EmailAddress)) // AmazonCustomerByEmail
1199 | $acp['acl'][] = array(
1200 | 'type' => 'AmazonCustomerByEmail',
1201 | 'email' => (string)$grantee->EmailAddress,
1202 | 'permission' => (string)$grant->Permission
1203 | );
1204 | elseif (isset($grantee->URI)) // Group
1205 | $acp['acl'][] = array(
1206 | 'type' => 'Group',
1207 | 'uri' => (string)$grantee->URI,
1208 | 'permission' => (string)$grant->Permission
1209 | );
1210 | else continue;
1211 | }
1212 | }
1213 | }
1214 | return $acp;
1215 | }
1216 |
1217 |
1218 | /**
1219 | * Delete an object
1220 | *
1221 | * @param string $bucket Bucket name
1222 | * @param string $uri Object URI
1223 | * @return boolean
1224 | */
1225 | public static function deleteObject($bucket, $uri)
1226 | {
1227 | $rest = new S3Request('DELETE', $bucket, $uri, self::$endpoint);
1228 | $rest = $rest->getResponse();
1229 | if ($rest->error === false && $rest->code !== 204)
1230 | $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1231 | if ($rest->error !== false)
1232 | {
1233 | self::__triggerError(sprintf("S3::deleteObject(): [%s] %s",
1234 | $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
1235 | return false;
1236 | }
1237 | return true;
1238 | }
1239 |
1240 |
1241 | /**
1242 | * Get a query string authenticated URL
1243 | *
1244 | * @param string $bucket Bucket name
1245 | * @param string $uri Object URI
1246 | * @param integer $lifetime Lifetime in seconds
1247 | * @param boolean $hostBucket Use the bucket name as the hostname
1248 | * @param boolean $https Use HTTPS ($hostBucket should be false for SSL verification)
1249 | * @return string
1250 | */
1251 | public static function getAuthenticatedURL($bucket, $uri, $lifetime, $hostBucket = false, $https = false)
1252 | {
1253 | $expires = self::__getTime() + $lifetime;
1254 | $uri = str_replace(array('%2F', '%2B'), array('/', '+'), rawurlencode($uri));
1255 | return sprintf(($https ? 'https' : 'http').'://%s/%s?AWSAccessKeyId=%s&Expires=%u&Signature=%s',
1256 | // $hostBucket ? $bucket : $bucket.'.s3.amazonaws.com', $uri, self::$__accessKey, $expires,
1257 | $hostBucket ? $bucket : self::$endpoint.'/'.$bucket, $uri, self::$__accessKey, $expires,
1258 | urlencode(self::__getHash("GET\n\n\n{$expires}\n/{$bucket}/{$uri}")));
1259 | }
1260 |
1261 |
1262 | /**
1263 | * Get a CloudFront signed policy URL
1264 | *
1265 | * @param array $policy Policy
1266 | * @return string
1267 | */
1268 | public static function getSignedPolicyURL($policy)
1269 | {
1270 | $data = json_encode($policy);
1271 | $signature = '';
1272 | if (!openssl_sign($data, $signature, self::$__signingKeyResource)) return false;
1273 |
1274 | $encoded = str_replace(array('+', '='), array('-', '_', '~'), base64_encode($data));
1275 | $signature = str_replace(array('+', '='), array('-', '_', '~'), base64_encode($signature));
1276 |
1277 | $url = $policy['Statement'][0]['Resource'] . '?';
1278 | foreach (array('Policy' => $encoded, 'Signature' => $signature, 'Key-Pair-Id' => self::$__signingKeyPairId) as $k => $v)
1279 | $url .= $k.'='.str_replace('%2F', '/', rawurlencode($v)).'&';
1280 | return substr($url, 0, -1);
1281 | }
1282 |
1283 |
1284 | /**
1285 | * Get a CloudFront canned policy URL
1286 | *
1287 | * @param string $url URL to sign
1288 | * @param integer $lifetime URL lifetime
1289 | * @return string
1290 | */
1291 | public static function getSignedCannedURL($url, $lifetime)
1292 | {
1293 | return self::getSignedPolicyURL(array(
1294 | 'Statement' => array(
1295 | array('Resource' => $url, 'Condition' => array(
1296 | 'DateLessThan' => array('AWS:EpochTime' => self::__getTime() + $lifetime)
1297 | ))
1298 | )
1299 | ));
1300 | }
1301 |
1302 |
1303 | /**
1304 | * Get upload POST parameters for form uploads
1305 | *
1306 | * @param string $bucket Bucket name
1307 | * @param string $uriPrefix Object URI prefix
1308 | * @param constant $acl ACL constant
1309 | * @param integer $lifetime Lifetime in seconds
1310 | * @param integer $maxFileSize Maximum filesize in bytes (default 5MB)
1311 | * @param string $successRedirect Redirect URL or 200 / 201 status code
1312 | * @param array $amzHeaders Array of x-amz-meta-* headers
1313 | * @param array $headers Array of request headers or content type as a string
1314 | * @param boolean $flashVars Includes additional "Filename" variable posted by Flash
1315 | * @return object
1316 | */
1317 | public static function getHttpUploadPostParams($bucket, $uriPrefix = '', $acl = self::ACL_PRIVATE, $lifetime = 3600,
1318 | $maxFileSize = 5242880, $successRedirect = "201", $amzHeaders = array(), $headers = array(), $flashVars = false)
1319 | {
1320 | // Create policy object
1321 | $policy = new stdClass;
1322 | $policy->expiration = gmdate('Y-m-d\TH:i:s\Z', (self::__getTime() + $lifetime));
1323 | $policy->conditions = array();
1324 | $obj = new stdClass; $obj->bucket = $bucket; array_push($policy->conditions, $obj);
1325 | $obj = new stdClass; $obj->acl = $acl; array_push($policy->conditions, $obj);
1326 |
1327 | $obj = new stdClass; // 200 for non-redirect uploads
1328 | if (is_numeric($successRedirect) && in_array((int)$successRedirect, array(200, 201)))
1329 | $obj->success_action_status = (string)$successRedirect;
1330 | else // URL
1331 | $obj->success_action_redirect = $successRedirect;
1332 | array_push($policy->conditions, $obj);
1333 |
1334 | if ($acl !== self::ACL_PUBLIC_READ)
1335 | array_push($policy->conditions, array('eq', '$acl', $acl));
1336 |
1337 | array_push($policy->conditions, array('starts-with', '$key', $uriPrefix));
1338 | if ($flashVars) array_push($policy->conditions, array('starts-with', '$Filename', ''));
1339 | foreach (array_keys($headers) as $headerKey)
1340 | array_push($policy->conditions, array('starts-with', '$'.$headerKey, ''));
1341 | foreach ($amzHeaders as $headerKey => $headerVal)
1342 | {
1343 | $obj = new stdClass;
1344 | $obj->{$headerKey} = (string)$headerVal;
1345 | array_push($policy->conditions, $obj);
1346 | }
1347 | array_push($policy->conditions, array('content-length-range', 0, $maxFileSize));
1348 | $policy = base64_encode(str_replace('\/', '/', json_encode($policy)));
1349 |
1350 | // Create parameters
1351 | $params = new stdClass;
1352 | $params->AWSAccessKeyId = self::$__accessKey;
1353 | $params->key = $uriPrefix.'${filename}';
1354 | $params->acl = $acl;
1355 | $params->policy = $policy; unset($policy);
1356 | $params->signature = self::__getHash($params->policy);
1357 | if (is_numeric($successRedirect) && in_array((int)$successRedirect, array(200, 201)))
1358 | $params->success_action_status = (string)$successRedirect;
1359 | else
1360 | $params->success_action_redirect = $successRedirect;
1361 | foreach ($headers as $headerKey => $headerVal) $params->{$headerKey} = (string)$headerVal;
1362 | foreach ($amzHeaders as $headerKey => $headerVal) $params->{$headerKey} = (string)$headerVal;
1363 | return $params;
1364 | }
1365 |
1366 |
1367 | /**
1368 | * Create a CloudFront distribution
1369 | *
1370 | * @param string $bucket Bucket name
1371 | * @param boolean $enabled Enabled (true/false)
1372 | * @param array $cnames Array containing CNAME aliases
1373 | * @param string $comment Use the bucket name as the hostname
1374 | * @param string $defaultRootObject Default root object
1375 | * @param string $originAccessIdentity Origin access identity
1376 | * @param array $trustedSigners Array of trusted signers
1377 | * @return array | false
1378 | */
1379 | public static function createDistribution($bucket, $enabled = true, $cnames = array(), $comment = null, $defaultRootObject = null, $originAccessIdentity = null, $trustedSigners = array())
1380 | {
1381 | if (!extension_loaded('openssl'))
1382 | {
1383 | self::__triggerError(sprintf("S3::createDistribution({$bucket}, ".(int)$enabled.", [], '$comment'): %s",
1384 | "CloudFront functionality requires SSL"), __FILE__, __LINE__);
1385 | return false;
1386 | }
1387 | $useSSL = self::$useSSL;
1388 |
1389 | self::$useSSL = true; // CloudFront requires SSL
1390 | $rest = new S3Request('POST', '', '2010-11-01/distribution', 'cloudfront.amazonaws.com');
1391 | $rest->data = self::__getCloudFrontDistributionConfigXML(
1392 | $bucket.'.s3.amazonaws.com',
1393 | $enabled,
1394 | (string)$comment,
1395 | (string)microtime(true),
1396 | $cnames,
1397 | $defaultRootObject,
1398 | $originAccessIdentity,
1399 | $trustedSigners
1400 | );
1401 |
1402 | $rest->size = strlen($rest->data);
1403 | $rest->setHeader('Content-Type', 'application/xml');
1404 | $rest = self::__getCloudFrontResponse($rest);
1405 |
1406 | self::$useSSL = $useSSL;
1407 |
1408 | if ($rest->error === false && $rest->code !== 201)
1409 | $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1410 | if ($rest->error !== false)
1411 | {
1412 | self::__triggerError(sprintf("S3::createDistribution({$bucket}, ".(int)$enabled.", [], '$comment'): [%s] %s",
1413 | $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
1414 | return false;
1415 | } elseif ($rest->body instanceof SimpleXMLElement)
1416 | return self::__parseCloudFrontDistributionConfig($rest->body);
1417 | return false;
1418 | }
1419 |
1420 |
1421 | /**
1422 | * Get CloudFront distribution info
1423 | *
1424 | * @param string $distributionId Distribution ID from listDistributions()
1425 | * @return array | false
1426 | */
1427 | public static function getDistribution($distributionId)
1428 | {
1429 | if (!extension_loaded('openssl'))
1430 | {
1431 | self::__triggerError(sprintf("S3::getDistribution($distributionId): %s",
1432 | "CloudFront functionality requires SSL"), __FILE__, __LINE__);
1433 | return false;
1434 | }
1435 | $useSSL = self::$useSSL;
1436 |
1437 | self::$useSSL = true; // CloudFront requires SSL
1438 | $rest = new S3Request('GET', '', '2010-11-01/distribution/'.$distributionId, 'cloudfront.amazonaws.com');
1439 | $rest = self::__getCloudFrontResponse($rest);
1440 |
1441 | self::$useSSL = $useSSL;
1442 |
1443 | if ($rest->error === false && $rest->code !== 200)
1444 | $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1445 | if ($rest->error !== false)
1446 | {
1447 | self::__triggerError(sprintf("S3::getDistribution($distributionId): [%s] %s",
1448 | $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
1449 | return false;
1450 | }
1451 | elseif ($rest->body instanceof SimpleXMLElement)
1452 | {
1453 | $dist = self::__parseCloudFrontDistributionConfig($rest->body);
1454 | $dist['hash'] = $rest->headers['hash'];
1455 | $dist['id'] = $distributionId;
1456 | return $dist;
1457 | }
1458 | return false;
1459 | }
1460 |
1461 |
1462 | /**
1463 | * Update a CloudFront distribution
1464 | *
1465 | * @param array $dist Distribution array info identical to output of getDistribution()
1466 | * @return array | false
1467 | */
1468 | public static function updateDistribution($dist)
1469 | {
1470 | if (!extension_loaded('openssl'))
1471 | {
1472 | self::__triggerError(sprintf("S3::updateDistribution({$dist['id']}): %s",
1473 | "CloudFront functionality requires SSL"), __FILE__, __LINE__);
1474 | return false;
1475 | }
1476 |
1477 | $useSSL = self::$useSSL;
1478 |
1479 | self::$useSSL = true; // CloudFront requires SSL
1480 | $rest = new S3Request('PUT', '', '2010-11-01/distribution/'.$dist['id'].'/config', 'cloudfront.amazonaws.com');
1481 | $rest->data = self::__getCloudFrontDistributionConfigXML(
1482 | $dist['origin'],
1483 | $dist['enabled'],
1484 | $dist['comment'],
1485 | $dist['callerReference'],
1486 | $dist['cnames'],
1487 | $dist['defaultRootObject'],
1488 | $dist['originAccessIdentity'],
1489 | $dist['trustedSigners']
1490 | );
1491 |
1492 | $rest->size = strlen($rest->data);
1493 | $rest->setHeader('If-Match', $dist['hash']);
1494 | $rest = self::__getCloudFrontResponse($rest);
1495 |
1496 | self::$useSSL = $useSSL;
1497 |
1498 | if ($rest->error === false && $rest->code !== 200)
1499 | $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1500 | if ($rest->error !== false)
1501 | {
1502 | self::__triggerError(sprintf("S3::updateDistribution({$dist['id']}): [%s] %s",
1503 | $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
1504 | return false;
1505 | } else {
1506 | $dist = self::__parseCloudFrontDistributionConfig($rest->body);
1507 | $dist['hash'] = $rest->headers['hash'];
1508 | return $dist;
1509 | }
1510 | return false;
1511 | }
1512 |
1513 |
1514 | /**
1515 | * Delete a CloudFront distribution
1516 | *
1517 | * @param array $dist Distribution array info identical to output of getDistribution()
1518 | * @return boolean
1519 | */
1520 | public static function deleteDistribution($dist)
1521 | {
1522 | if (!extension_loaded('openssl'))
1523 | {
1524 | self::__triggerError(sprintf("S3::deleteDistribution({$dist['id']}): %s",
1525 | "CloudFront functionality requires SSL"), __FILE__, __LINE__);
1526 | return false;
1527 | }
1528 |
1529 | $useSSL = self::$useSSL;
1530 |
1531 | self::$useSSL = true; // CloudFront requires SSL
1532 | $rest = new S3Request('DELETE', '', '2008-06-30/distribution/'.$dist['id'], 'cloudfront.amazonaws.com');
1533 | $rest->setHeader('If-Match', $dist['hash']);
1534 | $rest = self::__getCloudFrontResponse($rest);
1535 |
1536 | self::$useSSL = $useSSL;
1537 |
1538 | if ($rest->error === false && $rest->code !== 204)
1539 | $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1540 | if ($rest->error !== false)
1541 | {
1542 | self::__triggerError(sprintf("S3::deleteDistribution({$dist['id']}): [%s] %s",
1543 | $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
1544 | return false;
1545 | }
1546 | return true;
1547 | }
1548 |
1549 |
1550 | /**
1551 | * Get a list of CloudFront distributions
1552 | *
1553 | * @return array
1554 | */
1555 | public static function listDistributions()
1556 | {
1557 | if (!extension_loaded('openssl'))
1558 | {
1559 | self::__triggerError(sprintf("S3::listDistributions(): [%s] %s",
1560 | "CloudFront functionality requires SSL"), __FILE__, __LINE__);
1561 | return false;
1562 | }
1563 |
1564 | $useSSL = self::$useSSL;
1565 | self::$useSSL = true; // CloudFront requires SSL
1566 | $rest = new S3Request('GET', '', '2010-11-01/distribution', 'cloudfront.amazonaws.com');
1567 | $rest = self::__getCloudFrontResponse($rest);
1568 | self::$useSSL = $useSSL;
1569 |
1570 | if ($rest->error === false && $rest->code !== 200)
1571 | $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1572 | if ($rest->error !== false)
1573 | {
1574 | self::__triggerError(sprintf("S3::listDistributions(): [%s] %s",
1575 | $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
1576 | return false;
1577 | }
1578 | elseif ($rest->body instanceof SimpleXMLElement && isset($rest->body->DistributionSummary))
1579 | {
1580 | $list = array();
1581 | if (isset($rest->body->Marker, $rest->body->MaxItems, $rest->body->IsTruncated))
1582 | {
1583 | //$info['marker'] = (string)$rest->body->Marker;
1584 | //$info['maxItems'] = (int)$rest->body->MaxItems;
1585 | //$info['isTruncated'] = (string)$rest->body->IsTruncated == 'true' ? true : false;
1586 | }
1587 | foreach ($rest->body->DistributionSummary as $summary)
1588 | $list[(string)$summary->Id] = self::__parseCloudFrontDistributionConfig($summary);
1589 |
1590 | return $list;
1591 | }
1592 | return array();
1593 | }
1594 |
1595 | /**
1596 | * List CloudFront Origin Access Identities
1597 | *
1598 | * @return array
1599 | */
1600 | public static function listOriginAccessIdentities()
1601 | {
1602 | if (!extension_loaded('openssl'))
1603 | {
1604 | self::__triggerError(sprintf("S3::listOriginAccessIdentities(): [%s] %s",
1605 | "CloudFront functionality requires SSL"), __FILE__, __LINE__);
1606 | return false;
1607 | }
1608 |
1609 | self::$useSSL = true; // CloudFront requires SSL
1610 | $rest = new S3Request('GET', '', '2010-11-01/origin-access-identity/cloudfront', 'cloudfront.amazonaws.com');
1611 | $rest = self::__getCloudFrontResponse($rest);
1612 | $useSSL = self::$useSSL;
1613 |
1614 | if ($rest->error === false && $rest->code !== 200)
1615 | $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1616 | if ($rest->error !== false)
1617 | {
1618 | trigger_error(sprintf("S3::listOriginAccessIdentities(): [%s] %s",
1619 | $rest->error['code'], $rest->error['message']), E_USER_WARNING);
1620 | return false;
1621 | }
1622 |
1623 | if (isset($rest->body->CloudFrontOriginAccessIdentitySummary))
1624 | {
1625 | $identities = array();
1626 | foreach ($rest->body->CloudFrontOriginAccessIdentitySummary as $identity)
1627 | if (isset($identity->S3CanonicalUserId))
1628 | $identities[(string)$identity->Id] = array('id' => (string)$identity->Id, 's3CanonicalUserId' => (string)$identity->S3CanonicalUserId);
1629 | return $identities;
1630 | }
1631 | return false;
1632 | }
1633 |
1634 |
1635 | /**
1636 | * Invalidate objects in a CloudFront distribution
1637 | *
1638 | * Thanks to Martin Lindkvist for S3::invalidateDistribution()
1639 | *
1640 | * @param string $distributionId Distribution ID from listDistributions()
1641 | * @param array $paths Array of object paths to invalidate
1642 | * @return boolean
1643 | */
1644 | public static function invalidateDistribution($distributionId, $paths)
1645 | {
1646 | if (!extension_loaded('openssl'))
1647 | {
1648 | self::__triggerError(sprintf("S3::invalidateDistribution(): [%s] %s",
1649 | "CloudFront functionality requires SSL"), __FILE__, __LINE__);
1650 | return false;
1651 | }
1652 |
1653 | $useSSL = self::$useSSL;
1654 | self::$useSSL = true; // CloudFront requires SSL
1655 | $rest = new S3Request('POST', '', '2010-08-01/distribution/'.$distributionId.'/invalidation', 'cloudfront.amazonaws.com');
1656 | $rest->data = self::__getCloudFrontInvalidationBatchXML($paths, (string)microtime(true));
1657 | $rest->size = strlen($rest->data);
1658 | $rest = self::__getCloudFrontResponse($rest);
1659 | self::$useSSL = $useSSL;
1660 |
1661 | if ($rest->error === false && $rest->code !== 201)
1662 | $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1663 | if ($rest->error !== false)
1664 | {
1665 | trigger_error(sprintf("S3::invalidate('{$distributionId}',{$paths}): [%s] %s",
1666 | $rest->error['code'], $rest->error['message']), E_USER_WARNING);
1667 | return false;
1668 | }
1669 | return true;
1670 | }
1671 |
1672 |
1673 | /**
1674 | * Get a InvalidationBatch DOMDocument
1675 | *
1676 | * @internal Used to create XML in invalidateDistribution()
1677 | * @param array $paths Paths to objects to invalidateDistribution
1678 | * @param int $callerReference
1679 | * @return string
1680 | */
1681 | private static function __getCloudFrontInvalidationBatchXML($paths, $callerReference = '0')
1682 | {
1683 | $dom = new DOMDocument('1.0', 'UTF-8');
1684 | $dom->formatOutput = true;
1685 | $invalidationBatch = $dom->createElement('InvalidationBatch');
1686 | foreach ($paths as $path)
1687 | $invalidationBatch->appendChild($dom->createElement('Path', $path));
1688 |
1689 | $invalidationBatch->appendChild($dom->createElement('CallerReference', $callerReference));
1690 | $dom->appendChild($invalidationBatch);
1691 | return $dom->saveXML();
1692 | }
1693 |
1694 |
1695 | /**
1696 | * List your invalidation batches for invalidateDistribution() in a CloudFront distribution
1697 | *
1698 | * http://docs.amazonwebservices.com/AmazonCloudFront/latest/APIReference/ListInvalidation.html
1699 | * returned array looks like this:
1700 | * Array
1701 | * (
1702 | * [I31TWB0CN9V6XD] => InProgress
1703 | * [IT3TFE31M0IHZ] => Completed
1704 | * [I12HK7MPO1UQDA] => Completed
1705 | * [I1IA7R6JKTC3L2] => Completed
1706 | * )
1707 | *
1708 | * @param string $distributionId Distribution ID from listDistributions()
1709 | * @return array
1710 | */
1711 | public static function getDistributionInvalidationList($distributionId)
1712 | {
1713 | if (!extension_loaded('openssl'))
1714 | {
1715 | self::__triggerError(sprintf("S3::getDistributionInvalidationList(): [%s] %s",
1716 | "CloudFront functionality requires SSL"), __FILE__, __LINE__);
1717 | return false;
1718 | }
1719 |
1720 | $useSSL = self::$useSSL;
1721 | self::$useSSL = true; // CloudFront requires SSL
1722 | $rest = new S3Request('GET', '', '2010-11-01/distribution/'.$distributionId.'/invalidation', 'cloudfront.amazonaws.com');
1723 | $rest = self::__getCloudFrontResponse($rest);
1724 | self::$useSSL = $useSSL;
1725 |
1726 | if ($rest->error === false && $rest->code !== 200)
1727 | $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
1728 | if ($rest->error !== false)
1729 | {
1730 | trigger_error(sprintf("S3::getDistributionInvalidationList('{$distributionId}'): [%s]",
1731 | $rest->error['code'], $rest->error['message']), E_USER_WARNING);
1732 | return false;
1733 | }
1734 | elseif ($rest->body instanceof SimpleXMLElement && isset($rest->body->InvalidationSummary))
1735 | {
1736 | $list = array();
1737 | foreach ($rest->body->InvalidationSummary as $summary)
1738 | $list[(string)$summary->Id] = (string)$summary->Status;
1739 |
1740 | return $list;
1741 | }
1742 | return array();
1743 | }
1744 |
1745 |
1746 | /**
1747 | * Get a DistributionConfig DOMDocument
1748 | *
1749 | * http://docs.amazonwebservices.com/AmazonCloudFront/latest/APIReference/index.html?PutConfig.html
1750 | *
1751 | * @internal Used to create XML in createDistribution() and updateDistribution()
1752 | * @param string $bucket S3 Origin bucket
1753 | * @param boolean $enabled Enabled (true/false)
1754 | * @param string $comment Comment to append
1755 | * @param string $callerReference Caller reference
1756 | * @param array $cnames Array of CNAME aliases
1757 | * @param string $defaultRootObject Default root object
1758 | * @param string $originAccessIdentity Origin access identity
1759 | * @param array $trustedSigners Array of trusted signers
1760 | * @return string
1761 | */
1762 | private static function __getCloudFrontDistributionConfigXML($bucket, $enabled, $comment, $callerReference = '0', $cnames = array(), $defaultRootObject = null, $originAccessIdentity = null, $trustedSigners = array())
1763 | {
1764 | $dom = new DOMDocument('1.0', 'UTF-8');
1765 | $dom->formatOutput = true;
1766 | $distributionConfig = $dom->createElement('DistributionConfig');
1767 | $distributionConfig->setAttribute('xmlns', 'http://cloudfront.amazonaws.com/doc/2010-11-01/');
1768 |
1769 | $origin = $dom->createElement('S3Origin');
1770 | $origin->appendChild($dom->createElement('DNSName', $bucket));
1771 | if ($originAccessIdentity !== null) $origin->appendChild($dom->createElement('OriginAccessIdentity', $originAccessIdentity));
1772 | $distributionConfig->appendChild($origin);
1773 |
1774 | if ($defaultRootObject !== null) $distributionConfig->appendChild($dom->createElement('DefaultRootObject', $defaultRootObject));
1775 |
1776 | $distributionConfig->appendChild($dom->createElement('CallerReference', $callerReference));
1777 | foreach ($cnames as $cname)
1778 | $distributionConfig->appendChild($dom->createElement('CNAME', $cname));
1779 | if ($comment !== '') $distributionConfig->appendChild($dom->createElement('Comment', $comment));
1780 | $distributionConfig->appendChild($dom->createElement('Enabled', $enabled ? 'true' : 'false'));
1781 |
1782 | if (!empty($trustedSigners))
1783 | {
1784 | $trusted = $dom->createElement('TrustedSigners');
1785 | foreach ($trustedSigners as $id => $type)
1786 | $trusted->appendChild($id !== '' ? $dom->createElement($type, $id) : $dom->createElement($type));
1787 | $distributionConfig->appendChild($trusted);
1788 | }
1789 | $dom->appendChild($distributionConfig);
1790 | //var_dump($dom->saveXML());
1791 | return $dom->saveXML();
1792 | }
1793 |
1794 |
1795 | /**
1796 | * Parse a CloudFront distribution config
1797 | *
1798 | * See http://docs.amazonwebservices.com/AmazonCloudFront/latest/APIReference/index.html?GetDistribution.html
1799 | *
1800 | * @internal Used to parse the CloudFront DistributionConfig node to an array
1801 | * @param object &$node DOMNode
1802 | * @return array
1803 | */
1804 | private static function __parseCloudFrontDistributionConfig(&$node)
1805 | {
1806 | if (isset($node->DistributionConfig))
1807 | return self::__parseCloudFrontDistributionConfig($node->DistributionConfig);
1808 |
1809 | $dist = array();
1810 | if (isset($node->Id, $node->Status, $node->LastModifiedTime, $node->DomainName))
1811 | {
1812 | $dist['id'] = (string)$node->Id;
1813 | $dist['status'] = (string)$node->Status;
1814 | $dist['time'] = strtotime((string)$node->LastModifiedTime);
1815 | $dist['domain'] = (string)$node->DomainName;
1816 | }
1817 |
1818 | if (isset($node->CallerReference))
1819 | $dist['callerReference'] = (string)$node->CallerReference;
1820 |
1821 | if (isset($node->Enabled))
1822 | $dist['enabled'] = (string)$node->Enabled == 'true' ? true : false;
1823 |
1824 | if (isset($node->S3Origin))
1825 | {
1826 | if (isset($node->S3Origin->DNSName))
1827 | $dist['origin'] = (string)$node->S3Origin->DNSName;
1828 |
1829 | $dist['originAccessIdentity'] = isset($node->S3Origin->OriginAccessIdentity) ?
1830 | (string)$node->S3Origin->OriginAccessIdentity : null;
1831 | }
1832 |
1833 | $dist['defaultRootObject'] = isset($node->DefaultRootObject) ? (string)$node->DefaultRootObject : null;
1834 |
1835 | $dist['cnames'] = array();
1836 | if (isset($node->CNAME))
1837 | foreach ($node->CNAME as $cname)
1838 | $dist['cnames'][(string)$cname] = (string)$cname;
1839 |
1840 | $dist['trustedSigners'] = array();
1841 | if (isset($node->TrustedSigners))
1842 | foreach ($node->TrustedSigners as $signer)
1843 | {
1844 | if (isset($signer->Self))
1845 | $dist['trustedSigners'][''] = 'Self';
1846 | elseif (isset($signer->KeyPairId))
1847 | $dist['trustedSigners'][(string)$signer->KeyPairId] = 'KeyPairId';
1848 | elseif (isset($signer->AwsAccountNumber))
1849 | $dist['trustedSigners'][(string)$signer->AwsAccountNumber] = 'AwsAccountNumber';
1850 | }
1851 |
1852 | $dist['comment'] = isset($node->Comment) ? (string)$node->Comment : null;
1853 | return $dist;
1854 | }
1855 |
1856 |
1857 | /**
1858 | * Grab CloudFront response
1859 | *
1860 | * @internal Used to parse the CloudFront S3Request::getResponse() output
1861 | * @param object &$rest S3Request instance
1862 | * @return object
1863 | */
1864 | private static function __getCloudFrontResponse(&$rest)
1865 | {
1866 | $rest->getResponse();
1867 | if ($rest->response->error === false && isset($rest->response->body) &&
1868 | is_string($rest->response->body) && substr($rest->response->body, 0, 5) == 'response->body = simplexml_load_string($rest->response->body);
1871 | // Grab CloudFront errors
1872 | if (isset($rest->response->body->Error, $rest->response->body->Error->Code,
1873 | $rest->response->body->Error->Message))
1874 | {
1875 | $rest->response->error = array(
1876 | 'code' => (string)$rest->response->body->Error->Code,
1877 | 'message' => (string)$rest->response->body->Error->Message
1878 | );
1879 | unset($rest->response->body);
1880 | }
1881 | }
1882 | return $rest->response;
1883 | }
1884 |
1885 |
1886 | /**
1887 | * Get MIME type for file
1888 | *
1889 | * To override the putObject() Content-Type, add it to $requestHeaders
1890 | *
1891 | * To use fileinfo, ensure the MAGIC environment variable is set
1892 | *
1893 | * @internal Used to get mime types
1894 | * @param string &$file File path
1895 | * @return string
1896 | */
1897 | private static function __getMIMEType(&$file)
1898 | {
1899 | static $exts = array(
1900 | 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'gif' => 'image/gif',
1901 | 'png' => 'image/png', 'ico' => 'image/x-icon', 'pdf' => 'application/pdf',
1902 | 'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'svg' => 'image/svg+xml',
1903 | 'svgz' => 'image/svg+xml', 'swf' => 'application/x-shockwave-flash',
1904 | 'zip' => 'application/zip', 'gz' => 'application/x-gzip',
1905 | 'tar' => 'application/x-tar', 'bz' => 'application/x-bzip',
1906 | 'bz2' => 'application/x-bzip2', 'rar' => 'application/x-rar-compressed',
1907 | 'exe' => 'application/x-msdownload', 'msi' => 'application/x-msdownload',
1908 | 'cab' => 'application/vnd.ms-cab-compressed', 'txt' => 'text/plain',
1909 | 'asc' => 'text/plain', 'htm' => 'text/html', 'html' => 'text/html',
1910 | 'css' => 'text/css', 'js' => 'text/javascript',
1911 | 'xml' => 'text/xml', 'xsl' => 'application/xsl+xml',
1912 | 'ogg' => 'application/ogg', 'mp3' => 'audio/mpeg', 'wav' => 'audio/x-wav',
1913 | 'avi' => 'video/x-msvideo', 'mpg' => 'video/mpeg', 'mpeg' => 'video/mpeg',
1914 | 'mov' => 'video/quicktime', 'flv' => 'video/x-flv', 'php' => 'text/x-php'
1915 | );
1916 |
1917 | $ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
1918 | if (isset($exts[$ext])) return $exts[$ext];
1919 |
1920 | // Use fileinfo if available
1921 | if (extension_loaded('fileinfo') && isset($_ENV['MAGIC']) &&
1922 | ($finfo = finfo_open(FILEINFO_MIME, $_ENV['MAGIC'])) !== false)
1923 | {
1924 | if (($type = finfo_file($finfo, $file)) !== false)
1925 | {
1926 | // Remove the charset and grab the last content-type
1927 | $type = explode(' ', str_replace('; charset=', ';charset=', $type));
1928 | $type = array_pop($type);
1929 | $type = explode(';', $type);
1930 | $type = trim(array_shift($type));
1931 | }
1932 | finfo_close($finfo);
1933 | if ($type !== false && strlen($type) > 0) return $type;
1934 | }
1935 |
1936 | return 'application/octet-stream';
1937 | }
1938 |
1939 |
1940 | /**
1941 | * Get the current time
1942 | *
1943 | * @internal Used to apply offsets to sytem time
1944 | * @return integer
1945 | */
1946 | public static function __getTime()
1947 | {
1948 | return time() + self::$__timeOffset;
1949 | }
1950 |
1951 |
1952 | /**
1953 | * Generate the auth string: "AWS AccessKey:Signature"
1954 | *
1955 | * @internal Used by S3Request::getResponse()
1956 | * @param string $string String to sign
1957 | * @return string
1958 | */
1959 | public static function __getSignature($string)
1960 | {
1961 | return 'AWS '.self::$__accessKey.':'.self::__getHash($string);
1962 | }
1963 |
1964 |
1965 | /**
1966 | * Creates a HMAC-SHA1 hash
1967 | *
1968 | * This uses the hash extension if loaded
1969 | *
1970 | * @internal Used by __getSignature()
1971 | * @param string $string String to sign
1972 | * @return string
1973 | */
1974 | private static function __getHash($string)
1975 | {
1976 | return base64_encode(extension_loaded('hash') ?
1977 | hash_hmac('sha1', $string, self::$__secretKey, true) : pack('H*', sha1(
1978 | (str_pad(self::$__secretKey, 64, chr(0x00)) ^ (str_repeat(chr(0x5c), 64))) .
1979 | pack('H*', sha1((str_pad(self::$__secretKey, 64, chr(0x00)) ^
1980 | (str_repeat(chr(0x36), 64))) . $string)))));
1981 | }
1982 |
1983 |
1984 | /**
1985 | * Generate the headers for AWS Signature V4
1986 | *
1987 | * @internal Used by S3Request::getResponse()
1988 | * @param array $amzHeaders
1989 | * @param array $headers
1990 | * @param string $method
1991 | * @param string $uri
1992 | * @param array $parameters
1993 | * @return array
1994 | */
1995 | public static function __getSignatureV4($amzHeaders, $headers, $method, $uri, $parameters)
1996 | {
1997 | $service = 's3';
1998 | $region = S3::getRegion();
1999 |
2000 | $algorithm = 'AWS4-HMAC-SHA256';
2001 | $combinedHeaders = array();
2002 |
2003 | $amzDateStamp = substr($amzHeaders['x-amz-date'], 0, 8);
2004 |
2005 | // CanonicalHeaders
2006 | foreach ($headers as $k => $v)
2007 | $combinedHeaders[strtolower($k)] = trim($v);
2008 | foreach ($amzHeaders as $k => $v)
2009 | $combinedHeaders[strtolower($k)] = trim($v);
2010 | uksort($combinedHeaders, array('self', '__sortMetaHeadersCmp'));
2011 |
2012 | // Convert null query string parameters to strings and sort
2013 | $parameters = array_map('strval', $parameters);
2014 | uksort($parameters, array('self', '__sortMetaHeadersCmp'));
2015 | $queryString = http_build_query($parameters, null, '&', PHP_QUERY_RFC3986);
2016 |
2017 | // Payload
2018 | $amzPayload = array($method);
2019 |
2020 | $qsPos = strpos($uri, '?');
2021 | $amzPayload[] = ($qsPos === false ? $uri : substr($uri, 0, $qsPos));
2022 |
2023 | $amzPayload[] = $queryString;
2024 | // add header as string to requests
2025 | foreach ($combinedHeaders as $k => $v )
2026 | {
2027 | $amzPayload[] = $k . ':' . $v;
2028 | }
2029 | // add a blank entry so we end up with an extra line break
2030 | $amzPayload[] = '';
2031 | // SignedHeaders
2032 | $amzPayload[] = implode(';', array_keys($combinedHeaders));
2033 | // payload hash
2034 | $amzPayload[] = $amzHeaders['x-amz-content-sha256'];
2035 | // request as string
2036 | $amzPayloadStr = implode("\n", $amzPayload);
2037 |
2038 | // CredentialScope
2039 | $credentialScope = array($amzDateStamp, $region, $service, 'aws4_request');
2040 |
2041 | // stringToSign
2042 | $stringToSignStr = implode("\n", array($algorithm, $amzHeaders['x-amz-date'],
2043 | implode('/', $credentialScope), hash('sha256', $amzPayloadStr)));
2044 |
2045 | // Make Signature
2046 | $kSecret = 'AWS4' . self::$__secretKey;
2047 | $kDate = hash_hmac('sha256', $amzDateStamp, $kSecret, true);
2048 | $kRegion = hash_hmac('sha256', $region, $kDate, true);
2049 | $kService = hash_hmac('sha256', $service, $kRegion, true);
2050 | $kSigning = hash_hmac('sha256', 'aws4_request', $kService, true);
2051 |
2052 | $signature = hash_hmac('sha256', $stringToSignStr, $kSigning);
2053 |
2054 | return $algorithm . ' ' . implode(',', array(
2055 | 'Credential=' . self::$__accessKey . '/' . implode('/', $credentialScope),
2056 | 'SignedHeaders=' . implode(';', array_keys($combinedHeaders)),
2057 | 'Signature=' . $signature,
2058 | ));
2059 | }
2060 |
2061 |
2062 | /**
2063 | * Sort compare for meta headers
2064 | *
2065 | * @internal Used to sort x-amz meta headers
2066 | * @param string $a String A
2067 | * @param string $b String B
2068 | * @return integer
2069 | */
2070 | private static function __sortMetaHeadersCmp($a, $b)
2071 | {
2072 | $lenA = strlen($a);
2073 | $lenB = strlen($b);
2074 | $minLen = min($lenA, $lenB);
2075 | $ncmp = strncmp($a, $b, $minLen);
2076 | if ($lenA == $lenB) return $ncmp;
2077 | if (0 == $ncmp) return $lenA < $lenB ? -1 : 1;
2078 | return $ncmp;
2079 | }
2080 | }
2081 |
2082 | /**
2083 | * S3 Request class
2084 | *
2085 | * @link http://undesigned.org.za/2007/10/22/amazon-s3-php-class
2086 | * @version 0.5.0-dev
2087 | */
2088 | final class S3Request
2089 | {
2090 | /**
2091 | * AWS URI
2092 | *
2093 | * @var string
2094 | * @access private
2095 | */
2096 | private $endpoint;
2097 |
2098 | /**
2099 | * Verb
2100 | *
2101 | * @var string
2102 | * @access private
2103 | */
2104 | private $verb;
2105 |
2106 | /**
2107 | * S3 bucket name
2108 | *
2109 | * @var string
2110 | * @access private
2111 | */
2112 | private $bucket;
2113 |
2114 | /**
2115 | * Object URI
2116 | *
2117 | * @var string
2118 | * @access private
2119 | */
2120 | private $uri;
2121 |
2122 | /**
2123 | * Final object URI
2124 | *
2125 | * @var string
2126 | * @access private
2127 | */
2128 | private $resource = '';
2129 |
2130 | /**
2131 | * Additional request parameters
2132 | *
2133 | * @var array
2134 | * @access private
2135 | */
2136 | private $parameters = array();
2137 |
2138 | /**
2139 | * Amazon specific request headers
2140 | *
2141 | * @var array
2142 | * @access private
2143 | */
2144 | private $amzHeaders = array();
2145 |
2146 | /**
2147 | * HTTP request headers
2148 | *
2149 | * @var array
2150 | * @access private
2151 | */
2152 | private $headers = array(
2153 | 'Host' => '', 'Date' => '', 'Content-MD5' => '', 'Content-Type' => ''
2154 | );
2155 |
2156 | /**
2157 | * Use HTTP PUT?
2158 | *
2159 | * @var bool
2160 | * @access public
2161 | */
2162 | public $fp = false;
2163 |
2164 | /**
2165 | * PUT file size
2166 | *
2167 | * @var int
2168 | * @access public
2169 | */
2170 | public $size = 0;
2171 |
2172 | /**
2173 | * PUT post fields
2174 | *
2175 | * @var array
2176 | * @access public
2177 | */
2178 | public $data = false;
2179 |
2180 | /**
2181 | * S3 request respone
2182 | *
2183 | * @var object
2184 | * @access public
2185 | */
2186 | public $response;
2187 |
2188 |
2189 | /**
2190 | * Constructor
2191 | *
2192 | * @param string $verb Verb
2193 | * @param string $bucket Bucket name
2194 | * @param string $uri Object URI
2195 | * @param string $endpoint AWS endpoint URI
2196 | * @return mixed
2197 | */
2198 | function __construct($verb, $bucket = '', $uri = '', $endpoint = 's3.amazonaws.com')
2199 | {
2200 | $this->endpoint = $endpoint;
2201 | $this->verb = $verb;
2202 | $this->bucket = $bucket;
2203 | $this->uri = $uri !== '' ? '/'.str_replace('%2F', '/', rawurlencode($uri)) : '/';
2204 |
2205 | if ($this->bucket !== '')
2206 | {
2207 | if ($this->__dnsBucketName($this->bucket))
2208 | {
2209 | $this->headers['Host'] = $this->bucket.'.'.$this->endpoint;
2210 | $this->resource = '/'.$this->bucket.$this->uri;
2211 | }
2212 | else
2213 | {
2214 | // Old format, deprecated by AWS - removal scheduled for September 30th, 2020
2215 | $this->headers['Host'] = $this->endpoint;
2216 | $this->uri = $this->uri;
2217 | if ($this->bucket !== '') $this->uri = '/'.$this->bucket.$this->uri;
2218 | $this->bucket = '';
2219 | $this->resource = $this->uri;
2220 | }
2221 | }
2222 | else
2223 | {
2224 | $this->headers['Host'] = $this->endpoint;
2225 | $this->resource = $this->uri;
2226 | }
2227 |
2228 |
2229 | $this->headers['Date'] = gmdate('D, d M Y H:i:s T');
2230 | $this->response = new STDClass;
2231 | $this->response->error = false;
2232 | $this->response->body = null;
2233 | $this->response->headers = array();
2234 | }
2235 |
2236 |
2237 | /**
2238 | * Set request parameter
2239 | *
2240 | * @param string $key Key
2241 | * @param string $value Value
2242 | * @return void
2243 | */
2244 | public function setParameter($key, $value)
2245 | {
2246 | $this->parameters[$key] = $value;
2247 | }
2248 |
2249 |
2250 | /**
2251 | * Set request header
2252 | *
2253 | * @param string $key Key
2254 | * @param string $value Value
2255 | * @return void
2256 | */
2257 | public function setHeader($key, $value)
2258 | {
2259 | $this->headers[$key] = $value;
2260 | }
2261 |
2262 |
2263 | /**
2264 | * Set x-amz-meta-* header
2265 | *
2266 | * @param string $key Key
2267 | * @param string $value Value
2268 | * @return void
2269 | */
2270 | public function setAmzHeader($key, $value)
2271 | {
2272 | $this->amzHeaders[$key] = $value;
2273 | }
2274 |
2275 |
2276 | /**
2277 | * Get the S3 response
2278 | *
2279 | * @return object | false
2280 | */
2281 | public function getResponse()
2282 | {
2283 | $query = '';
2284 | if (sizeof($this->parameters) > 0)
2285 | {
2286 | $query = substr($this->uri, -1) !== '?' ? '?' : '&';
2287 | foreach ($this->parameters as $var => $value)
2288 | if ($value == null || $value == '') $query .= $var.'&';
2289 | else $query .= $var.'='.rawurlencode($value).'&';
2290 | $query = substr($query, 0, -1);
2291 | $this->uri .= $query;
2292 |
2293 | if (array_key_exists('acl', $this->parameters) ||
2294 | array_key_exists('location', $this->parameters) ||
2295 | array_key_exists('torrent', $this->parameters) ||
2296 | array_key_exists('website', $this->parameters) ||
2297 | array_key_exists('logging', $this->parameters))
2298 | $this->resource .= $query;
2299 | }
2300 | $url = (S3::$useSSL ? 'https://' : 'http://') . ($this->headers['Host'] !== '' ? $this->headers['Host'] : $this->endpoint) . $this->uri;
2301 |
2302 | // Basic setup
2303 | $curl = curl_init();
2304 | curl_setopt($curl, CURLOPT_USERAGENT, 'S3/php');
2305 |
2306 | if (S3::$useSSL)
2307 | {
2308 | // Set protocol version
2309 | curl_setopt($curl, CURLOPT_SSLVERSION, S3::$useSSLVersion);
2310 |
2311 | // SSL Validation can now be optional for those with broken OpenSSL installations
2312 | curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, S3::$useSSLValidation ? 2 : 0);
2313 | curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, S3::$useSSLValidation ? 1 : 0);
2314 |
2315 | if (S3::$sslKey !== null) curl_setopt($curl, CURLOPT_SSLKEY, S3::$sslKey);
2316 | if (S3::$sslCert !== null) curl_setopt($curl, CURLOPT_SSLCERT, S3::$sslCert);
2317 | if (S3::$sslCACert !== null) curl_setopt($curl, CURLOPT_CAINFO, S3::$sslCACert);
2318 | }
2319 |
2320 | curl_setopt($curl, CURLOPT_URL, $url);
2321 |
2322 | if (S3::$proxy != null && isset(S3::$proxy['host']))
2323 | {
2324 | curl_setopt($curl, CURLOPT_PROXY, S3::$proxy['host']);
2325 | curl_setopt($curl, CURLOPT_PROXYTYPE, S3::$proxy['type']);
2326 | if (isset(S3::$proxy['user'], S3::$proxy['pass']) && S3::$proxy['user'] != null && S3::$proxy['pass'] != null)
2327 | curl_setopt($curl, CURLOPT_PROXYUSERPWD, sprintf('%s:%s', S3::$proxy['user'], S3::$proxy['pass']));
2328 | }
2329 |
2330 | // Headers
2331 | $httpHeaders = array();
2332 | if (S3::hasAuth())
2333 | {
2334 | // Authorization string (CloudFront stringToSign should only contain a date)
2335 | if ($this->headers['Host'] == 'cloudfront.amazonaws.com')
2336 | {
2337 | # TODO: Update CloudFront authentication
2338 | foreach ($this->amzHeaders as $header => $value)
2339 | if (strlen($value) > 0) $httpHeaders[] = $header.': '.$value;
2340 |
2341 | foreach ($this->headers as $header => $value)
2342 | if (strlen($value) > 0) $httpHeaders[] = $header.': '.$value;
2343 |
2344 | $httpHeaders[] = 'Authorization: ' . S3::__getSignature($this->headers['Date']);
2345 | }
2346 | else
2347 | {
2348 | $this->amzHeaders['x-amz-date'] = gmdate('Ymd\THis\Z');
2349 |
2350 | if (!isset($this->amzHeaders['x-amz-content-sha256']))
2351 | $this->amzHeaders['x-amz-content-sha256'] = hash('sha256', $this->data);
2352 |
2353 | foreach ($this->amzHeaders as $header => $value)
2354 | if (strlen($value) > 0) $httpHeaders[] = $header.': '.$value;
2355 |
2356 | foreach ($this->headers as $header => $value)
2357 | if (strlen($value) > 0) $httpHeaders[] = $header.': '.$value;
2358 |
2359 | $httpHeaders[] = 'Authorization: ' . S3::__getSignatureV4(
2360 | $this->amzHeaders,
2361 | $this->headers,
2362 | $this->verb,
2363 | $this->uri,
2364 | $this->parameters
2365 | );
2366 |
2367 | }
2368 | }
2369 |
2370 | curl_setopt($curl, CURLOPT_HTTPHEADER, $httpHeaders);
2371 | curl_setopt($curl, CURLOPT_HEADER, false);
2372 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, false);
2373 | curl_setopt($curl, CURLOPT_WRITEFUNCTION, array(&$this, '__responseWriteCallback'));
2374 | curl_setopt($curl, CURLOPT_HEADERFUNCTION, array(&$this, '__responseHeaderCallback'));
2375 | curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
2376 |
2377 | // Request types
2378 | switch ($this->verb)
2379 | {
2380 | case 'GET': break;
2381 | case 'PUT': case 'POST': // POST only used for CloudFront
2382 | if ($this->fp !== false)
2383 | {
2384 | curl_setopt($curl, CURLOPT_PUT, true);
2385 | curl_setopt($curl, CURLOPT_INFILE, $this->fp);
2386 | if ($this->size >= 0)
2387 | curl_setopt($curl, CURLOPT_INFILESIZE, $this->size);
2388 | }
2389 | elseif ($this->data !== false)
2390 | {
2391 | curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb);
2392 | curl_setopt($curl, CURLOPT_POSTFIELDS, $this->data);
2393 | }
2394 | else
2395 | curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb);
2396 | break;
2397 | case 'HEAD':
2398 | curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'HEAD');
2399 | curl_setopt($curl, CURLOPT_NOBODY, true);
2400 | break;
2401 | case 'DELETE':
2402 | curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE');
2403 | break;
2404 | default: break;
2405 | }
2406 |
2407 | // set curl progress function callback
2408 | if (S3::$progressFunction) {
2409 | curl_setopt($curl, CURLOPT_NOPROGRESS, false);
2410 | curl_setopt($curl, CURLOPT_PROGRESSFUNCTION, S3::$progressFunction);
2411 | }
2412 |
2413 | // Execute, grab errors
2414 | if (curl_exec($curl))
2415 | $this->response->code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
2416 | else
2417 | $this->response->error = array(
2418 | 'code' => curl_errno($curl),
2419 | 'message' => curl_error($curl),
2420 | 'resource' => $this->resource
2421 | );
2422 |
2423 | @curl_close($curl);
2424 |
2425 | // Parse body into XML
2426 | if ($this->response->error === false && isset($this->response->headers['type']) &&
2427 | $this->response->headers['type'] == 'application/xml' && isset($this->response->body))
2428 | {
2429 | $this->response->body = simplexml_load_string($this->response->body);
2430 |
2431 | // Grab S3 errors
2432 | if (!in_array($this->response->code, array(200, 204, 206)) &&
2433 | isset($this->response->body->Code, $this->response->body->Message))
2434 | {
2435 | $this->response->error = array(
2436 | 'code' => (string)$this->response->body->Code,
2437 | 'message' => (string)$this->response->body->Message
2438 | );
2439 | if (isset($this->response->body->Resource))
2440 | $this->response->error['resource'] = (string)$this->response->body->Resource;
2441 | unset($this->response->body);
2442 | }
2443 | }
2444 |
2445 | // Clean up file resources
2446 | if ($this->fp !== false && is_resource($this->fp)) fclose($this->fp);
2447 |
2448 | return $this->response;
2449 | }
2450 |
2451 |
2452 | /**
2453 | * CURL write callback
2454 | *
2455 | * @param resource &$curl CURL resource
2456 | * @param string &$data Data
2457 | * @return integer
2458 | */
2459 | private function __responseWriteCallback(&$curl, &$data)
2460 | {
2461 | if (in_array($this->response->code, array(200, 206)) && $this->fp !== false)
2462 | return fwrite($this->fp, $data);
2463 | else
2464 | $this->response->body .= $data;
2465 | return strlen($data);
2466 | }
2467 |
2468 |
2469 | /**
2470 | * Check DNS conformity
2471 | *
2472 | * @param string $bucket Bucket name
2473 | * @return boolean
2474 | */
2475 | private function __dnsBucketName($bucket)
2476 | {
2477 | if (strlen($bucket) > 63 || preg_match("/[^a-z0-9\.-]/", $bucket) > 0) return false;
2478 | if (S3::$useSSL && strstr($bucket, '.') !== false) return false;
2479 | if (strstr($bucket, '-.') !== false) return false;
2480 | if (strstr($bucket, '..') !== false) return false;
2481 | if (!preg_match("/^[0-9a-z]/", $bucket)) return false;
2482 | if (!preg_match("/[0-9a-z]$/", $bucket)) return false;
2483 | return true;
2484 | }
2485 |
2486 |
2487 | /**
2488 | * CURL header callback
2489 | *
2490 | * @param resource $curl CURL resource
2491 | * @param string $data Data
2492 | * @return integer
2493 | */
2494 | private function __responseHeaderCallback($curl, $data)
2495 | {
2496 | if (($strlen = strlen($data)) <= 2) return $strlen;
2497 | if (substr($data, 0, 4) == 'HTTP')
2498 | $this->response->code = (int)substr($data, 9, 3);
2499 | else
2500 | {
2501 | $data = trim($data);
2502 | if (strpos($data, ': ') === false) return $strlen;
2503 | list($header, $value) = explode(': ', $data, 2);
2504 | $header = strtolower($header);
2505 | if ($header == 'last-modified')
2506 | $this->response->headers['time'] = strtotime($value);
2507 | elseif ($header == 'date')
2508 | $this->response->headers['date'] = strtotime($value);
2509 | elseif ($header == 'content-length')
2510 | $this->response->headers['size'] = (int)$value;
2511 | elseif ($header == 'content-type')
2512 | $this->response->headers['type'] = $value;
2513 | elseif ($header == 'etag')
2514 | $this->response->headers['hash'] = $value{0} == '"' ? substr($value, 1, -1) : $value;
2515 | elseif (preg_match('/^x-amz-meta-.*$/', $header))
2516 | $this->response->headers[$header] = $value;
2517 | }
2518 | return $strlen;
2519 | }
2520 |
2521 | }
2522 |
2523 | /**
2524 | * S3 exception class
2525 | *
2526 | * @link http://undesigned.org.za/2007/10/22/amazon-s3-php-class
2527 | * @version 0.5.0-dev
2528 | */
2529 |
2530 | class S3Exception extends Exception {
2531 | /**
2532 | * Class constructor
2533 | *
2534 | * @param string $message Exception message
2535 | * @param string $file File in which exception was created
2536 | * @param string $line Line number on which exception was created
2537 | * @param int $code Exception code
2538 | */
2539 | function __construct($message, $file, $line, $code = 0)
2540 | {
2541 | parent::__construct($message, $code);
2542 | $this->file = $file;
2543 | $this->line = $line;
2544 | }
2545 | }
2546 |
--------------------------------------------------------------------------------