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