Source for file Storage3.php

Documentation is available at Storage3.php


1 <?php
2 /**
3 * A PHP5 class for interfacing with the Amazon S3 API
4 *
5 * @package Storage3
6 * @category Storage3
7 */
8
9 /**
10 *
11 */
12 require_once './Crypt/HMAC.php';
13 require_once './HTTP/Request.php';
14 require_once './Net/URL.php';
15 require_once './Net/Socket.php';
16
17 /**
18 * This class is the abstraction layer through which you provide easy access to the Amazon S3 API
19 *
20 * Takes ($key, $secret, $url)
21 * - [str] $key: Your Amazon Web Services Access Key ID
22 * - [str] $secret: Your Amazon Web Services Secret Access Key
23 * - [str] $url: OPTIONAL (default: http://s3.amazonaws.com/)
24 *
25 * Example Usage:
26 *
27 * <pre>
28 * $s3=new storage3("aslkdfjhalskdfjhals", "sdalkjfhaslkjdfhaslkdhjfaslkjdhflas");
29 *
30 * $s3->lsBuckets();
31 * $s3->lsFiles("apokalyptik2");
32 *
33 * $s3->mkBucket("apokalyptik2");
34 * $s3->mkFile("blah do blah", "apokalyptik2", "test", "binary/octet-stream", time()+300);
35 *
36 * $s3->getFile('apokalyptik2', 'test');
37 *
38 * $s3->rmFile('apokalyptik2', "test");
39 * $s3->rmBucket('apokalyptik2');
40 * </pre>
41 */
42 class Storage3 {
43 /**#@+
44 * @ignore
45 */
46 var $accessUrl;
47 var $accessKeyId;
48 var $accessSecret;
49 /**#@-*/
50
51 /**
52 * Constructor
53 *
54 * Takes ($key, $secret, $url)
55 *
56 * - [str] $key: Your Amazon Web Services "Access Key ID"
57 * - [str] $secret: Your Amazon Web Services "Secret Access Key"
58 * - [str] $url: OPTIONAL: defaults: http://s3.amazonaws.com/
59 *
60 * @ignore
61 */
62 function __construct($key, $secret, $url="http://s3.amazonaws.com/") {
63 $this->accessUrl=$url;
64 $this->accessKeyId=$key;
65 $this->accessSecret=$secret;
66 }
67
68 /**
69 * Get a list of all your s3 buckets (directories)
70 *
71 */
72 function lsBuckets() {
73 $httpDate = gmdate(DATE_RFC822);
74 $tosign="GET\n\n\n{$httpDate}\n/";
75 $authSigned="AWS {$this->accessKeyId}:".$this->strSign($tosign);
76 $this->authUnSigned="AWS {$this->accessKeyId}:".$tosign;
77 $req =& new HTTP_Request($this->accessUrl);
78 $req->addHeader("Date", $httpDate);
79 $req->addHeader("Authorization", $authSigned);
80 $req->sendRequest();
81 $this->responseStr=$req->getResponseBody();
82 $this->responseNice=$this->errorTranslate($req->getResponseCode());
83 $this->responseHeaders=$req->_response->_headers;
84 if ($this->responseInt != 200) {
85 $this->_parseErrorStr();
86 return(FALSE);
87 } else {
88 return($this->_parseBucketList());
89 }
90 }
91
92
93 /**
94 * Retrieve a list of all files from a bucket (directory)
95 *
96 * Takes ($s3bucket)
97 *
98 * - [str] $s3bucket: the bucket/directory which you would like a listing of
99 */
100 function lsFiles($s3bucket, $pageKey=FALSE, $maxKeys=500) {
101 $httpDate = gmdate(DATE_RFC822);
102 $tosign="GET\n\n\n{$httpDate}\n/".$s3bucket;
103 $authSigned="AWS {$this->accessKeyId}:".$this->strSign($tosign);
104 $this->authUnSigned="AWS {$this->accessKeyId}:".$tosign;
105 if ( $pageKey ) {
106 $req =& new HTTP_Request($this->accessUrl.$s3bucket."?max-keys={$maxKeys}&marker={$pageKey}");
107 } else {
108 $req =& new HTTP_Request($this->accessUrl.$s3bucket."?max-keys={$maxKeys}");
109 }
110 $req->addHeader("Date", $httpDate);
111 $req->addHeader("Authorization", $authSigned);
112 $req->sendRequest();
113 $this->responseStr=$req->getResponseBody();
114 $this->responseNice=$this->errorTranslate($req->getResponseCode());
115 $this->responseHeaders=$req->_response->_headers;
116 if ($this->responseInt != 200) {
117 $this->_parseErrorStr();
118 return(FALSE);
119 } else {
120 $rval=$this->_parseFileList();
121 $is_more=array_shift($rval);
122 if ( $is_more == "true" ) {
123 $nrval=$this->lsFiles($s3bucket, $rval[(count($rval)-1)], $maxKeys);
124 if ( is_array($nrval) ) {
125 return(array_merge($rval, $nrval));
126 }
127 } else {
128 return($rval);
129 }
130 }
131 }
132
133 /**
134 * Delete a file object from your s3 account S3
135 *
136 * Takes ($s3bucket, $s3file)
137 *
138 * - [str] $s3bucket: the bucket/directory from which you would like the file
139 * - [str] $s3file: the file which you would like from the bucket/directory
140 */
141 function rmFile($s3bucket, $s3file) {
142 $httpDate = gmdate(DATE_RFC822);
143 $tosign="DELETE\n\n\n{$httpDate}\n/".$s3bucket.'/'.$s3file;
144 $authSigned="AWS {$this->accessKeyId}:".$this->strSign($tosign);
145 $this->authUnSigned="AWS {$this->accessKeyId}:".$tosign;
146 $req =& new HTTP_Request($this->accessUrl.$s3bucket.'/'.$s3file);
147 $req->setMethod("DELETE");
148 $req->addHeader("Date", $httpDate);
149 $req->addHeader("Authorization", $authSigned);
150 $req->sendRequest();
151 $this->responseStr=$req->getResponseBody();
152 $this->responseNice=$this->errorTranslate($req->getResponseCode());
153 $this->responseHeaders=$req->_response->_headers;
154 // The response for a sucessful delete is, apparently, 204 :D
155 if ($this->responseInt != 204) {
156 $this->_parseErrorStr();
157 return(FALSE);
158 } else {
159 return(TRUE);
160 }
161 }
162
163 /**
164 * Delete an empty s3 bucket/directory. (The operation will fail if you attempt this on a bucket which is not empty)
165 *
166 * Takes ($s3bucket, $recursive)
167 *
168 * - [str] $s3bucket: the bucket/directory which you would like to delete from s3.
169 * - [int] $recursive: OPTIONAL: DEFAULTS: 0, if set to 1 it will delete all files inside the bucket, and then delete the bucket. This is because attempting to delete a bucket which is not empty gives an error 409 "BucketNotEmpty" ("The bucket you tried to delete is not empty")
170 */
171 function rmBucket($s3bucket, $recursive=0) {
172 if ( ! is_array($this->lsFiles($s3bucket)) ) {
173 return(TRUE);
174 }
175 if ( $recursive == 1 ) {
176 while( count($this->lsFiles($s3bucket)) > 0 ) {
177 $files=$this->lsFiles($s3bucket);
178 foreach ( $files as $val ) {
179 $this->rmFile($s3bucket, $val);
180 }
181 }
182 }
183 $httpDate = gmdate(DATE_RFC822);
184 $tosign="DELETE\n\n\n{$httpDate}\n/".$s3bucket;
185 $authSigned="AWS {$this->accessKeyId}:".$this->strSign($tosign);
186 $authUnSigned="AWS {$this->accessKeyId}:".$tosign;
187 $req =& new HTTP_Request($this->accessUrl.$s3bucket);
188 $req->setMethod("DELETE");
189 $req->addHeader("Date", $httpDate);
190 $req->addHeader("Authorization", $authSigned);
191 $req->sendRequest();
192 $this->responseStr=$req->getResponseBody();
193 $this->responseNice=$this->errorTranslate($req->getResponseCode());
194 $this->responseHeaders=$req->_response->_headers;
195 if ($this->responseInt != 204) {
196 $this->_parseErrorStr();
197 return(FALSE);
198 } else {
199 return(TRUE);
200 }
201 }
202
203 /**
204 * Save data from s3 into a local file
205 *
206 * Takes ($s3bucket, $s3file, $fileName)
207 *
208 * - [str] $s3bucket: The bucket/directory which holds the file
209 * - [str] $s3file: The file, from, the bucket/directory which to retrieve
210 * - [str] $fileName: The local filename in which to save your data
211 */
212 function saveFile($s3bucket, $s3file, $fileName) {
213 $data=$this->getFile($s3bucket, $s3file);
214 if ( $data ) {
215 return(file_put_contents($fileName, $data));
216 } else {
217 return(FALSE);
218 }
219 }
220
221 /**
222 * Get data from an S3 file object and return it as a string
223 *
224 * Takes ($s3bucket, $s3file)
225 *
226 * - [str] $s3bucket: The bucket/directory which holds the file
227 * - [str] $s3file: The file, from, the bucket/directory which to retrieve
228 */
229 function getFile($s3bucket, $s3file) {
230 $httpDate = gmdate(DATE_RFC822);
231 $tosign="GET\n\n\n{$httpDate}\n/".$s3bucket.'/'.$s3file;
232 $authSigned="AWS {$this->accessKeyId}:".$this->strSign($tosign);
233 $authUnSigned="AWS {$this->accessKeyId}:".$tosign;
234 $req =& new HTTP_Request($this->accessUrl.$s3bucket.'/'.$s3file);
235 $req->addHeader("Date", $httpDate);
236 $req->addHeader("Authorization", $authSigned);
237 $req->sendRequest();
238 $this->responseStr=$req->getResponseBody();
239 $this->responseNice=$this->errorTranslate($req->getResponseCode());
240 $this->responseHeaders=$req->_response->_headers;
241 if ($this->responseInt != 200) {
242 $this->_parseErrorStr();
243 return(FALSE);
244 } else {
245 return($this->responseStr);
246 }
247 }
248
249 /**
250 * Make an Amazon S3 container object (directory)
251 *
252 * Takes ($s3bucket)
253 *
254 * - [str] $s3bucket: the bucket/directory you wish to create
255 */
256 function mkBucket($s3bucket) {
257 $content_md5='';
258 $content_type='';
259 $httpDate = gmdate(DATE_RFC822);
260 $tosign="PUT\n\n\n{$httpDate}\n/${s3bucket}";
261 $authSigned="AWS {$this->accessKeyId}:".$this->strSign($tosign);
262 $authUnSigned="AWS {$this->accessKeyId}:".$tosign;
263 $authPlain="AWS {$this->accessKeyId}:".$tosign;
264 $req =& new HTTP_Request($this->accessUrl . $s3bucket);
265 $req->setMethod("PUT");
266 $req->addHeader("Content-Length", 0);
267 $req->addHeader("Date", $httpDate);
268 $req->addHeader("Authorization", $authSigned);
269 $req->sendRequest();
270 $this->responseStr=$req->getResponseBody();
271 $this->responseNice=$this->errorTranslate($req->getResponseCode());
272 $this->responseHeaders=$req->_response->_headers;
273 if ( ($this->responseInt != 200) ) {
274 $this->_parseErrorStr();
275 return(FALSE);
276 } else {
277 return(TRUE);
278 }
279 }
280
281 /**
282 * Read a file from the filesystem and put it into S3
283 *
284 * Takes ($string, $s3bucket, $s3file, $contentType, $contentExpires, $contentDisposition, $cacheControl)
285 *
286 * - [str] $fileName: The file contents you wish to write to the object file
287 * - [str] $s3bucket: the bucket/directory you wish to store this file in
288 * - [str] $s3file: the name of the file object in which you would like to store data in side the bucket/directory
289 * - [str] $contentType: OPTIONAL: defaults: binary/octet-stream; see http://s3.amazonaws.com/doc/s3-developer-guide/RESTObjectPUT.html
290 * - [str] $contentExpires: OPTIONAL: defaults: OFF, the unix timestamp at which the file expires, see http://s3.amazonaws.com/doc/s3-developer-guide/RESTObjectPUT.html
291 * - [str] $contentDisposition: OPTIONAL: defaults: OFF, see http://s3.amazonaws.com/doc/s3-developer-guide/RESTObjectPUT.html
292 * - [str] $cacheControl: OPTIONAL: defaults: OFF, see http://s3.amazonaws.com/doc/s3-developer-guide/RESTObjectPUT.html
293 */
294 function putFile($fileName, $s3bucket, $s3file, $contentType="binary/octet-stream", $contentExpires=FALSE, $contentDisposition=FALSE, $cacheControl=FALSE) {
295 $content=file_get_contents($fileName);
296 return($this->mkFile($content, $s3bucket, $s3file, $contentType, $contentExpires, $contentDisposition, $cacheControl));
297 }
298
299 /**
300 * Put data (in the form of a passed string) into an S3 file object. If you specify an object to which you have write privileges and already exists... this will overwrite that object
301 *
302 * Takes ($string, $s3bucket, $s3file, $contentType, $contentExpires, $contentDisposition, $cacheControl)
303 *
304 * - [str] $string: The file contents you wish to write to the object file
305 * - [str] $s3bucket: the bucket/directory you wish to store this file in
306 * - [str] $s3file: the name of the file object in which you would like to store data in side the bucket/directory
307 * - [str] $contentType: OPTIONAL: defaults: binary/octet-stream; see http://s3.amazonaws.com/doc/s3-developer-guide/RESTObjectPUT.html
308 * - [str] $contentExpires: OPTIONAL: defaults: OFF, the unix timestamp at which the file expires, see http://s3.amazonaws.com/doc/s3-developer-guide/RESTObjectPUT.html
309 * - [str] $contentDisposition: OPTIONAL: defaults: OFF, see http://s3.amazonaws.com/doc/s3-developer-guide/RESTObjectPUT.html
310 * - [str] $cacheControl: OPTIONAL: defaults: OFF, see http://s3.amazonaws.com/doc/s3-developer-guide/RESTObjectPUT.html
311 */
312 function mkFile($string, $s3bucket, $s3file, $contentType="binary/octet-stream", $contentExpires=FALSE, $contentDisposition=FALSE, $cacheControl=FALSE, $defaultAcl=FALSE) {
313 $req =& new HTTP_Request($this->accessUrl . $s3bucket .'/'. $s3file);
314 $req->setMethod("PUT");
315 $httpDate = gmdate(DATE_RFC822);
316 $contentLength=strlen($string);
317
318 // Mandatory headers
319 $tosign[]="PUT";
320 // TODO: Impliment md5 checksums
321 $tosign[]='';
322 // $contentMd5=md5($string, TRUE);
323 // $contentMd5=$this->md5_base64($string);
324 // $tosign[]=$contentMd5;
325 // $req->addHeader("Content-MD5", $contentMd5);
326 if ( $contentType ) {
327 $tosign[]=$contentType;
328 $req->addHeader("Content-Type", $contentType);
329 } else {
330 $tosign[]="";
331 }
332 $tosign[]=$httpDate;
333 $tosign[]='/'.$s3bucket.'/'.$s3file;
334 $req->addHeader("Date", $httpDate);
335 $req->addHeader("Content-Length", $contentLength);
336 $this->authUnSigned="AWS {$this->accessKeyId}:".implode("\n", $tosign);
337 $authSigned="AWS {$this->accessKeyId}:".$this->strSign(implode("\n", $tosign));
338 $req->addHeader("Authorization", $authSigned);
339
340 // Optional Headers
341 if ( $contentExpires ) {
342 $req->addHeader("Expires", $contentExpires);
343 }
344 if ( $contentDisposition ) {
345 $req->addHeader("Content-Disposition", $contentDisposition);
346 }
347 if ( $cacheControl ) {
348 $req->addHeader("Cache-Control", $cacheControl);
349 }
350
351 // TODO: Impliment arbitrary headers via associative array
352
353 $req->setBody($string);
354 $req->sendRequest();
355
356 $this->responseStr=$req->getResponseBody();
357 $this->responseNice=$this->errorTranslate($req->getResponseCode());
358 $this->responseHeaders=$req->_response->_headers;
359 if ($this->responseInt != 200) {
360 $this->_parseErrorStr();
361 return(FALSE);
362 } else {
363 if ( $defaultAcl ) {
364 return($this->setACL($s3bucket, $s3file, $shorthand=$defaultAcl));
365 } else {
366 return(TRUE);
367 }
368 }
369 }
370
371
372 /**
373 * Find out if an object already exists in s3
374 *
375 * - Takes ($s3bucket, $s3file)
376 * - Returns bool (TRUE: exists, FALSE: doesn't exist)
377 *
378 * - [str] $s3bucket, REQUIRED, the bucket in which the target object is located
379 * - [str] $s3file, REQUIRED, the object on which you wish to check
380 */
381 function fileExists($s3bucket, $s3file) {
382 $req =& new HTTP_Request($this->accessUrl . $s3bucket .'/'. $s3file);
383 $req->setMethod("HEAD");
384 $httpDate = gmdate(DATE_RFC822);
385 $tosign[]="HEAD";
386 $tosign[]='';
387 $tosign[]='';
388 $tosign[]=$httpDate;
389 $tosign[]='/'.$s3bucket.'/'.$s3file;
390 $req->addHeader("Date", $httpDate);
391 $this->authUnSigned="AWS {$this->accessKeyId}:".implode("\n", $tosign);
392 $authSigned="AWS {$this->accessKeyId}:".$this->strSign(implode("\n", $tosign));
393 $req->addHeader("Authorization", $authSigned);
394 $req->setBody($string);
395 $req->sendRequest();
396 $this->responseStr=$req->getResponseBody();
397 $this->responseNice=$this->errorTranslate($req->getResponseCode());
398 $this->responseHeaders=$req->_response->_headers;
399 if ($this->responseInt != 200) {
400 $this->_parseErrorStr();
401 return(FALSE);
402 } else {
403 return(TRUE);
404 }
405
406 }
407
408 /**
409 * Set the acl for a file stored in S3
410 *
411 * - Takes ($s3bucket, $s3file, $shorthand)
412 * - Returns bool TRUE|FALSE
413 *
414 * - [str] $s3bucket, REQUIRED, the bucket in which the target object is located
415 * - [str] $s3file, REQUIRED, the object on which you wish to set the ACL
416 * - [str] $shorthand, OPTIONAL, DEFAULT: "public-read"
417 *
418 * Contributed by "Nick" (http://www.katanapg.com/) April 5th, 2006 at 9:52 pm e
419 */
420 function setACL($s3bucket, $s3file, $shorthand="public-read") {
421 $s3file = $s3file . "?acl";
422 $req =& new HTTP_Request($this->accessUrl . $s3bucket ."/". $s3file);
423 //echo $this->accessUrl . $s3bucket ./. $s3file;
424 $req->setMethod("PUT");
425 $httpDate = gmdate(DATE_RFC822);
426 // Mandatory headers
427 $tosign[]="PUT";
428 // TODO: Impliment md5 checksums
429 $tosign[]="";
430 $tosign[]="";
431 $tosign[]=$httpDate;
432 $tosign[]="x-amz-acl:" . $shorthand;
433 $tosign[]="/".$s3bucket."/".$s3file;
434 $req->addHeader("Date", $httpDate);
435 $this->authUnSigned="AWS {$this->accessKeyId}:".implode("\n", $tosign);
436 $authSigned="AWS {$this->accessKeyId}:".$this->strSign(implode("\n", $tosign));
437 $req->addHeader("Authorization", $authSigned);
438 $req->addHeader("x-amz-acl", $shorthand);
439 $req->sendRequest();
440 $this->responseStr=$req->getResponseBody();
441 $this->responseNice=$this->errorTranslate($req->getResponseCode());
442 if ($this->responseInt != 200) {
443 $this->_parseErrorStr();
444 return(FALSE);
445 } else {
446 return(TRUE);
447 }
448 }
449
450
451
452 /**
453 * Take the XMl returned from amazon s3 and make an array out of it
454 *
455 * @ignore
456 */
457 function _parseBucketList() {
458 $parser=simplexml_load_string($this->responseStr);
459 $rval=array();
460 foreach ( $parser->Buckets->Bucket as $bucket ) {
461 list(, $dir) = each($bucket);
462 $rval[]=$dir;
463 }
464 return($rval);
465 }
466
467 /**
468 * Take the XMl returned from amazon s3 and make an array out of it
469 *
470 * @ignore
471 */
472 function _parseFileList() {
473 $parser=simplexml_load_string($this->responseStr);
474 $rval=array();
475 list(, $IsTruncated)=each($parser->IsTruncated);
476 $rval["IsTruncated"]=$IsTruncated;
477 foreach ( $parser->Contents as $item ) {
478 list(, $file)=each($item->Key);
479 $rval[]=$file;
480 }
481 return($rval);
482 }
483
484 /**
485 * Take the XMl returned from amazon s3 and make an array out of it
486 *
487 * @ignore
488 */
489 function _parseErrorStr() {
490 $parser=simplexml_load_string($this->responseStr);
491 if ( $parser ) {
492 foreach ( $parser as $line => $val ) {
493 list(, $value)=each($val);
494 $rval[$line]=$value;
495 }
496 }
497 if ( isset($rval) ) {
498 $this->responseArray=$rval;
499 } else {
500 return(TRUE);
501 }
502 }
503
504 /**
505 * Convert a hex string to a base64 string (stolen from the example)
506 *
507 * @IGNORE
508 */
509 function hex2b64($str) {
510 $raw = '';
511 for ($i=0; $i < strlen($str); $i+=2) {
512 $raw .= chr(hexdec(substr($str, $i, 2)));
513 }
514 return base64_encode($raw);
515 }
516
517 /**
518 * Sign a string, as directed by the s3 api (stolen from the example)
519 *
520 * @ignore
521 */
522 function strSign($str) {
523 $hasher =& new Crypt_HMAC($this->accessSecret, "sha1");
524 $signature = $this->hex2b64($hasher->hash($str));
525 return($signature);
526 }
527
528 /**
529 * May be used later on to determine how the class behaves when encountering particular response codes.
530 *
531 * @ignore
532 */
533 function errorTranslate($int) {
534 $this->responseInt=$int;
535 }
536
537 /**
538 * Tell me why something failed!
539 *
540 * Returns an array of the values returned, via the xml output, from the s3 service
541 */
542 function why() {
543 if ( $this->responseInt != 200 ) {
544 $rval=$this->responseArray;
545 $rval['int']=$this->responseInt;
546 return($rval);
547 } else {
548 $rval['int']=$this->responseInt;
549 return($rval);
550 }
551 }
552
553 }

Documentation generated on Tue, 02 May 2006 13:33:11 -0700 by phpDocumentor 1.2.3