Blame | Letzte Änderung | Log anzeigen | RSS feed
<?php/** Copyright 2011-2012 Amazon.com, Inc. or its affiliates. All Rights Reserved.** Licensed under the Apache License, Version 2.0 (the "License").* You may not use this file except in compliance with the License.* A copy of the License is located at** http://aws.amazon.com/apache2.0** or in the "license" file accompanying this file. This file is distributed* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either* express or implied. See the License for the specific language governing* permissions and limitations under the License.*//*** Provides an interface for accessing Amazon S3 using PHP's native file management functions.** Amazon S3 file patterns take the following form: <code>s3://bucket/object</code>.*/class S3StreamWrapper{/*** @var array An array of AmazonS3 clients registered as stream wrappers.*/protected static $_clients = array();/*** Registers the S3StreamWrapper class as a stream wrapper.** @param AmazonS3 $s3 (Optional) An instance of the AmazonS3 client.* @param string $protocol (Optional) The name of the protocol to register.* @return boolean Whether or not the registration succeeded.*/public static function register(AmazonS3 $s3 = null, $protocol = 's3'){S3StreamWrapper::$_clients[$protocol] = $s3 ? $s3 : new AmazonS3();return stream_wrapper_register($protocol, 'S3StreamWrapper');}/*** Makes the given token PCRE-compatible.** @param string $token (Required) The token* @return string The PCRE-compatible version of the token*/public static function regex_token($token){$token = str_replace('/', '\/', $token);$token = quotemeta($token);return str_replace('\\\\', '\\', $token);}public $position = 0;public $path = null;public $file_list = null;public $open_file = null;public $seek_position = 0;public $eof = false;public $buffer = null;public $object_size = 0;/*** Fetches the client for the protocol being used.** @param string $protocol (Optional) The protocol associated with this stream wrapper.* @return AmazonS3 The S3 client associated with this stream wrapper.*/public function client($protocol = null){if ($protocol == null){if ($parsed = parse_url($this->path)){$protocol = $parsed['scheme'];}else{trigger_error(__CLASS__ . ' could not determine the protocol of the stream wrapper in use.');}}return self::$_clients[$protocol];}/*** Parses an S3 URL into the parts needed by the stream wrapper.** @param string $path The path to parse.* @return array An array of 3 items: protocol, bucket, and object name ready for <code>list()</code>.*/public function parse_path($path){$url = parse_url($path);return array($url['scheme'], // Protocol$url['host'], // Bucket(isset($url['path']) ? substr($url['path'], 1) : ''), // Object);}/*** Close directory handle. This method is called in response to <php:closedir()>.** Since Amazon S3 doesn't have real directories, always return <code>true</code>.** @return boolean*/public function dir_closedir(){$this->position = 0;$this->path = null;$this->file_list = null;$this->open_file = null;$this->seek_position = 0;$this->eof = false;$this->buffer = null;$this->object_size = 0;return true;}/*** Open directory handle. This method is called in response to <php:opendir()>.** @param string $path (Required) Specifies the URL that was passed to <php:opendir()>.* @param integer $options (Required) Not used. Passed in by <php:opendir()>.* @return boolean Returns <code>true</code> on success or <code>false</code> on failure.*/public function dir_opendir($path, $options){$this->path = $path;list($protocol, $bucket, $object_name) = $this->parse_path($path);$pattern = '/^' . self::regex_token($object_name) . '(.*)[^\/$]/';$this->file_list = $this->client($protocol)->get_object_list($bucket, array('pcre' => $pattern));return (count($this->file_list)) ? true : false;}/*** This method is called in response to <php:readdir()>.** @return string Should return a string representing the next filename, or <code>false</code> if there is no next file.*/public function dir_readdir(){if (isset($this->file_list[$this->position])){$out = $this->file_list[$this->position];$this->position++;}else{$out = false;}return $out;}/*** This method is called in response to <php:rewinddir()>.** Should reset the output generated by <php:streamWrapper::dir_readdir()>. i.e.: The next call to* <php:streamWrapper::dir_readdir()> should return the first entry in the location returned by* <php:streamWrapper::dir_opendir()>.** @return boolean Returns <code>true</code> on success or <code>false</code> on failure.*/public function dir_rewinddir(){$this->position = 0;return true;}/*** Create a new bucket. This method is called in response to <php:mkdir()>.** @param string $path (Required) The bucket name to create.* @param integer $mode (Optional) Permissions. 700-range permissions map to ACL_PUBLIC. 600-range permissions map to ACL_AUTH_READ. All other permissions map to ACL_PRIVATE. Expects octal form.* @param integer $options (Optional) Ignored.* @return boolean Whether the bucket was created successfully or not.*/public function mkdir($path, $mode, $options){// Get the value that was *actually* passed in as mode, and default to 0$trace_slice = array_slice(debug_backtrace(), -1);$mode = isset($trace_slice[0]['args'][1]) ? decoct($trace_slice[0]['args'][1]) : 0;$this->path = $path;list($protocol, $bucket, $object_name) = $this->parse_path($path);if (in_array($mode, range(700, 799))){$acl = AmazonS3::ACL_PUBLIC;}elseif (in_array($mode, range(600, 699))){$acl = AmazonS3::ACL_AUTH_READ;}else{$acl = AmazonS3::ACL_PRIVATE;}$client = $this->client($protocol);$region = $client->hostname;$response = $client->create_bucket($bucket, $region, $acl);return $response->isOK();}/*** Renames a file or directory. This method is called in response to <php:rename()>.** @param string $path_from (Required) The URL to the current file.* @param string $path_to (Required) The URL which the <code>$path_from</code> should be renamed to.* @return boolean Returns <code>true</code> on success or <code>false</code> on failure.*/public function rename($path_from, $path_to){list($protocol, $from_bucket_name, $from_object_name) = $this->parse_path($path_from);list($protocol, $to_bucket_name, $to_object_name) = $this->parse_path($path_to);$copy_response = $this->client($protocol)->copy_object(array('bucket' => $from_bucket_name, 'filename' => $from_object_name),array('bucket' => $to_bucket_name, 'filename' => $to_object_name ));if ($copy_response->isOK()){$delete_response = $this->client($protocol)->delete_object($from_bucket_name, $from_object_name);if ($delete_response->isOK()){return true;}}return false;}/*** This method is called in response to <php:rmdir()>.** @param string $path (Required) The bucket name to create.* @param boolean $context (Optional) Ignored.* @return boolean Whether the bucket was deleted successfully or not.*/public function rmdir($path, $context){$this->path = $path;list($protocol, $bucket, $object_name) = $this->parse_path($path);$response = $this->client($protocol)->delete_bucket($bucket);return $response->isOK();}/*** NOT IMPLEMENTED!** @param integer $cast_as* @return resource*/// public function stream_cast($cast_as) {}/*** Close a resource. This method is called in response to <php:fclose()>.** All resources that were locked, or allocated, by the wrapper should be released.** @return void*/public function stream_close(){$this->position = 0;$this->path = null;$this->file_list = null;$this->open_file = null;$this->seek_position = 0;$this->eof = false;$this->buffer = null;$this->object_size = 0;}/*** Tests for end-of-file on a file pointer. This method is called in response to <php:feof()>.** @return boolean*/public function stream_eof(){return $this->eof;}/*** Flushes the output. This method is called in response to <php:fflush()>. If you have cached data in* your stream but not yet stored it into the underlying storage, you should do so now.** Since this implementation doesn't buffer streams, simply return <code>true</code>.** @return boolean Whether or not flushing succeeded*/public function stream_flush(){if ($this->buffer === null){return false;}list($protocol, $bucket, $object_name) = $this->parse_path($this->path);$response = $this->client($protocol)->create_object($bucket, $object_name, array('body' => $this->buffer,));$this->seek_position = 0;$this->buffer = null;$this->eof = true;return $response->isOK();}/*** This method is called in response to <php:flock()>, when <php:file_put_contents()> (when flags contains* <code>LOCK_EX</code>), <php:stream_set_blocking()> and when closing the stream (<code>LOCK_UN</code>).** Not implemented in S3, so it's not implemented here.** @param mode $operation* @return boolean*/// public function stream_lock($operation) {}/*** Opens file or URL. This method is called immediately after the wrapper is initialized* (e.g., by <php:fopen()> and <php:file_get_contents()>).** @param string $path (Required) Specifies the URL that was passed to the original function.* @param string $mode (Required) Ignored.* @param integer $options (Required) Ignored.* @param string &$opened_path (Required) Returns the same value as was passed into <code>$path</code>.* @return boolean Returns <code>true</code> on success or <code>false</code> on failure.*/public function stream_open($path, $mode, $options, &$opened_path){$opened_path = $path;$this->open_file = $path;$this->path = $path;$this->seek_position = 0;$this->object_size = 0;return true;}/*** Read from stream. This method is called in response to <php:fread()> and <php:fgets()>.**** It is important to avoid reading files that are near to or larger than the amount of memory* allocated to PHP, otherwise "out of memory" errors will occur.** @param integer $count (Required) Always equal to 8192. PHP is fun, isn't it?* @return string The contents of the Amazon S3 object.*/public function stream_read($count){if ($this->eof){return false;}list($protocol, $bucket, $object_name) = $this->parse_path($this->path);if ($this->seek_position > 0 && $this->object_size){if ($count + $this->seek_position > $this->object_size){$count = $this->object_size - $this->seek_position;}$start = $this->seek_position;$end = $this->seek_position + $count;$response = $this->client($protocol)->get_object($bucket, $object_name, array('range' => $start . '-' . $end));}else{$response = $this->client($protocol)->get_object($bucket, $object_name);$this->object_size = isset($response->header['content-length']) ? $response->header['content-length'] : 0;}if (!$response->isOK()){return false;}$data = substr($response->body, 0, min($count, $this->object_size));$this->seek_position += strlen($data);if ($this->seek_position >= $this->object_size){$this->eof = true;$this->seek_position = 0;$this->object_size = 0;}return $data;}/*** Seeks to specific location in a stream. This method is called in response to <php:fseek()>. The read/write* position of the stream should be updated according to the <code>$offset</code> and <code>$whence</code>* parameters.** @param integer $offset (Required) The number of bytes to offset from the start of the file.* @param integer $whence (Optional) Ignored. Always uses <code>SEEK_SET</code>.* @return boolean Whether or not the seek was successful.*/public function stream_seek($offset, $whence){$this->seek_position = $offset;return true;}/*** @param integer $option* @param integer $arg1* @param integer $arg2* @return boolean*/// public function stream_set_option($option, $arg1, $arg2) {}/*** Retrieve information about a file resource.** @return array Returns the same data as a call to <php:stat()>.*/public function stream_stat(){return $this->url_stat($this->path, null);}/*** Retrieve the current position of a stream. This method is called in response to <php:ftell()>.** @return integer Returns the current position of the stream.*/public function stream_tell(){return $this->seek_position;}/*** Write to stream. This method is called in response to <php:fwrite()>.** It is important to avoid reading files that are larger than the amount of memory allocated to PHP,* otherwise "out of memory" errors will occur.** @param string $data (Required) The data to write to the stream.* @return integer The number of bytes that were written to the stream.*/public function stream_write($data){$size = strlen($data);$this->seek_position = $size;$this->buffer .= $data;return $this->seek_position;}/*** Delete a file. This method is called in response to <php:unlink()>.** @param string $path (Required) The file URL which should be deleted.* @return boolean Returns <code>true</code> on success or <code>false</code> on failure.*/public function unlink($path){$this->path = $path;list($protocol, $bucket, $object_name) = $this->parse_path($path);$response = $this->client($protocol)->delete_object($bucket, $object_name);return $response->isOK();}/*** This method is called in response to all <php:stat()> related functions.** @param string $path (Required) The file path or URL to stat. Note that in the case of a URL, it must be a <code>://</code> delimited URL. Other URL forms are not supported.* @param integer $flags (Required) Holds additional flags set by the streams API. This implementation ignores all defined flags.* @return array Should return as many elements as <php:stat()> does. Unknown or unavailable values should be set to a rational value (usually <code>0</code>).*/public function url_stat($path, $flags){// Defaults$out = array();$out[0] = $out['dev'] = 0;$out[1] = $out['ino'] = 0;$out[2] = $out['mode'] = 0;$out[3] = $out['nlink'] = 0;$out[4] = $out['uid'] = 0;$out[5] = $out['gid'] = 0;$out[6] = $out['rdev'] = 0;$out[7] = $out['size'] = 0;$out[8] = $out['atime'] = 0;$out[9] = $out['mtime'] = 0;$out[10] = $out['ctime'] = 0;$out[11] = $out['blksize'] = 0;$out[12] = $out['blocks'] = 0;$this->path = $path;list($protocol, $bucket, $object_name) = $this->parse_path($this->path);$file = null;$mode = 0;if ($object_name){$response = $this->client($protocol)->list_objects($bucket, array('prefix' => $object_name));if (!$response->isOK()){return $out;}// Ummm... yeah...if (is_object($response->body)){$file = $response->body->Contents[0];}else{$body = simplexml_load_string($response->body);$file = $body->Contents[0];}}else{$response = $this->client($protocol)->list_objects($bucket);if (!$response->isOK()){return $out;}}/*Type & Permission bitwise values (only those that pertain to S3).Simulate the concept of a "directory". Nothing has an executable bit because there's no executing on S3.Reference: http://docstore.mik.ua/orelly/webprog/pcook/ch19_13.htm0100000 => type: regular file0040000 => type: directory0000400 => owner: read permission0000200 => owner: write permission0000040 => group: read permission0000020 => group: write permission0000004 => others: read permission0000002 => others: write permission*/// File or directory?// @todo: Add more detailed support for permissions. Currently only takes OWNER into account.if (!$object_name) // Root of the bucket{$mode = octdec('0040777');}elseif ($file){$mode = (str_replace('//', '/', $object_name . '/') === (string) $file->Key) ? octdec('0040777') : octdec('0100777'); // Directory, Owner R/W : Regular File, Owner R/W}else{$mode = octdec('0100777');}// Update stat output$out[2] = $out['mode'] = $mode;$out[4] = $out['uid'] = (isset($file) ? (string) $file->Owner->ID : 0);$out[7] = $out['size'] = (isset($file) ? (string) $file->Size : 0);$out[8] = $out['atime'] = (isset($file) ? date('U', strtotime((string) $file->LastModified)) : 0);$out[9] = $out['mtime'] = (isset($file) ? date('U', strtotime((string) $file->LastModified)) : 0);$out[10] = $out['ctime'] = (isset($file) ? date('U', strtotime((string) $file->LastModified)) : 0);return $out;}}