File indexing completed on 2024-12-29 05:28:01

0001 <?php
0002 /**
0003  * Zend Framework
0004  *
0005  * LICENSE
0006  *
0007  * This source file is subject to the new BSD license that is bundled
0008  * with this package in the file LICENSE.txt.
0009  * It is also available through the world-wide-web at this URL:
0010  * http://framework.zend.com/license/new-bsd
0011  * If you did not receive a copy of the license and are unable to
0012  * obtain it through the world-wide-web, please send an email
0013  * to license@zend.com so we can send you a copy immediately.
0014  *
0015  * @category   Zend
0016  * @package    Zend_Service
0017  * @subpackage Amazon_S3
0018  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0019  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0020  * @version    $Id$
0021  */
0022 
0023 /**
0024  * @see Zend_Service_Amazon_S3
0025  */
0026 // require_once 'Zend/Service/Amazon/S3.php';
0027 
0028 /**
0029  * Amazon S3 PHP stream wrapper
0030  *
0031  * @category   Zend
0032  * @package    Zend_Service
0033  * @subpackage Amazon_S3
0034  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0035  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0036  */
0037 class Zend_Service_Amazon_S3_Stream
0038 {
0039     /**
0040      * @var boolean Write the buffer on fflush()?
0041      */
0042     private $_writeBuffer = false;
0043 
0044     /**
0045      * @var integer Current read/write position
0046      */
0047     private $_position = 0;
0048 
0049     /**
0050      * @var integer Total size of the object as returned by S3 (Content-length)
0051      */
0052     private $_objectSize = 0;
0053 
0054     /**
0055      * @var string File name to interact with
0056      */
0057     private $_objectName = null;
0058 
0059     /**
0060      * @var string Current read/write buffer
0061      */
0062     private $_objectBuffer = null;
0063 
0064     /**
0065      * @var array Available buckets
0066      */
0067     private $_bucketList = array();
0068 
0069     /**
0070      * @var Zend_Service_Amazon_S3
0071      */
0072     private $_s3 = null;
0073 
0074     /**
0075      * Retrieve client for this stream type
0076      *
0077      * @param  string $path
0078      * @return Zend_Service_Amazon_S3
0079      */
0080     protected function _getS3Client($path)
0081     {
0082         if ($this->_s3 === null) {
0083             $url = explode(':', $path);
0084 
0085             if (!$url) {
0086                 /**
0087                  * @see Zend_Service_Amazon_S3_Exception
0088                  */
0089                 // require_once 'Zend/Service/Amazon/S3/Exception.php';
0090                 throw new Zend_Service_Amazon_S3_Exception("Unable to parse URL $path");
0091             }
0092 
0093             $this->_s3 = Zend_Service_Amazon_S3::getWrapperClient($url[0]);
0094             if (!$this->_s3) {
0095                 /**
0096                  * @see Zend_Service_Amazon_S3_Exception
0097                  */
0098                 // require_once 'Zend/Service/Amazon/S3/Exception.php';
0099                 throw new Zend_Service_Amazon_S3_Exception("Unknown client for wrapper {$url[0]}");
0100             }
0101         }
0102 
0103         return $this->_s3;
0104     }
0105 
0106     /**
0107      * Extract object name from URL
0108      *
0109      * @param string $path
0110      * @return string
0111      */
0112     protected function _getNamePart($path)
0113     {
0114         $url = parse_url($path);
0115         if ($url['host']) {
0116             return !empty($url['path']) ? $url['host'].$url['path'] : $url['host'];
0117         }
0118 
0119         return '';
0120     }
0121     /**
0122      * Open the stream
0123      *
0124      * @param  string  $path
0125      * @param  string  $mode
0126      * @param  integer $options
0127      * @param  string  $opened_path
0128      * @return boolean
0129      */
0130     public function stream_open($path, $mode, $options, $opened_path)
0131     {
0132         $name = $this->_getNamePart($path);
0133         // If we open the file for writing, just return true. Create the object
0134         // on fflush call
0135         if (strpbrk($mode, 'wax')) {
0136             $this->_objectName = $name;
0137             $this->_objectBuffer = null;
0138             $this->_objectSize = 0;
0139             $this->_position = 0;
0140             $this->_writeBuffer = true;
0141             $this->_getS3Client($path);
0142             return true;
0143         } else {
0144             // Otherwise, just see if the file exists or not
0145             $info = $this->_getS3Client($path)->getInfo($name);
0146             if ($info) {
0147                 $this->_objectName = $name;
0148                 $this->_objectBuffer = null;
0149                 $this->_objectSize = $info['size'];
0150                 $this->_position = 0;
0151                 $this->_writeBuffer = false;
0152                 $this->_getS3Client($path);
0153                 return true;
0154             }
0155         }
0156         return false;
0157     }
0158 
0159     /**
0160      * Close the stream
0161      *
0162      * @return void
0163      */
0164     public function stream_close()
0165     {
0166         $this->_objectName = null;
0167         $this->_objectBuffer = null;
0168         $this->_objectSize = 0;
0169         $this->_position = 0;
0170         $this->_writeBuffer = false;
0171         unset($this->_s3);
0172     }
0173 
0174     /**
0175      * Read from the stream
0176      *
0177      * http://bugs.php.net/21641 - stream_read() is always passed PHP's
0178      * internal read buffer size (8192) no matter what is passed as $count
0179      * parameter to fread().
0180      *
0181      * @param  integer $count
0182      * @return string
0183      */
0184     public function stream_read($count)
0185     {
0186         if (!$this->_objectName) {
0187             return false;
0188         }
0189 
0190         // make sure that count doesn't exceed object size
0191         if ($count + $this->_position > $this->_objectSize) {
0192             $count = $this->_objectSize - $this->_position;
0193         }
0194 
0195         $range_start = $this->_position;
0196         $range_end   = $this->_position + $count - 1;
0197 
0198         // Only fetch more data from S3 if we haven't fetched any data yet (postion=0)
0199         // OR, the range end position plus 1 is greater than the size of the current
0200         // object buffer
0201         if ($this->_objectBuffer === null  ||  $range_end >= strlen($this->_objectBuffer)) {
0202             $headers = array(
0203                 'Range' => "bytes=$range_start-$range_end"
0204             );
0205 
0206             $response = $this->_s3->_makeRequest('GET', $this->_objectName, null, $headers);
0207 
0208             if ($response->getStatus() == 206) { // 206 Partial Content
0209                 $this->_objectBuffer .= $response->getBody();
0210             }
0211         }
0212 
0213         $data = substr($this->_objectBuffer, $this->_position, $count);
0214         $this->_position += strlen($data);
0215         return $data;
0216     }
0217 
0218     /**
0219      * Write to the stream
0220      *
0221      * @param  string $data
0222      * @return integer
0223      */
0224     public function stream_write($data)
0225     {
0226         if (!$this->_objectName) {
0227             return 0;
0228         }
0229         $len = strlen($data);
0230         $this->_objectBuffer .= $data;
0231         $this->_objectSize += $len;
0232         // TODO: handle current position for writing!
0233         return $len;
0234     }
0235 
0236     /**
0237      * End of the stream?
0238      *
0239      * @return boolean
0240      */
0241     public function stream_eof()
0242     {
0243         if (!$this->_objectName) {
0244             return true;
0245         }
0246 
0247         return ($this->_position >= $this->_objectSize);
0248     }
0249 
0250     /**
0251      * What is the current read/write position of the stream
0252      *
0253      * @return integer
0254      */
0255     public function stream_tell()
0256     {
0257         return $this->_position;
0258     }
0259 
0260     /**
0261      * Update the read/write position of the stream
0262      *
0263      * @param  integer $offset
0264      * @param  integer $whence
0265      * @return boolean
0266      */
0267     public function stream_seek($offset, $whence)
0268     {
0269         if (!$this->_objectName) {
0270             return false;
0271         }
0272 
0273         switch ($whence) {
0274             case SEEK_CUR:
0275                 // Set position to current location plus $offset
0276                 $new_pos = $this->_position + $offset;
0277                 break;
0278             case SEEK_END:
0279                 // Set position to end-of-file plus $offset
0280                 $new_pos = $this->_objectSize + $offset;
0281                 break;
0282             case SEEK_SET:
0283             default:
0284                 // Set position equal to $offset
0285                 $new_pos = $offset;
0286                 break;
0287         }
0288         $ret = ($new_pos >= 0 && $new_pos <= $this->_objectSize);
0289         if ($ret) {
0290             $this->_position = $new_pos;
0291         }
0292         return $ret;
0293     }
0294 
0295     /**
0296      * Flush current cached stream data to storage
0297      *
0298      * @return boolean
0299      */
0300     public function stream_flush()
0301     {
0302         // If the stream wasn't opened for writing, just return false
0303         if (!$this->_writeBuffer) {
0304             return false;
0305         }
0306 
0307         $ret = $this->_s3->putObject($this->_objectName, $this->_objectBuffer);
0308 
0309         $this->_objectBuffer = null;
0310 
0311         return $ret;
0312     }
0313 
0314     /**
0315      * Returns data array of stream variables
0316      *
0317      * @return array
0318      */
0319     public function stream_stat()
0320     {
0321         if (!$this->_objectName) {
0322             return false;
0323         }
0324 
0325         $stat = array();
0326         $stat['dev'] = 0;
0327         $stat['ino'] = 0;
0328         $stat['mode'] = 0777;
0329         $stat['nlink'] = 0;
0330         $stat['uid'] = 0;
0331         $stat['gid'] = 0;
0332         $stat['rdev'] = 0;
0333         $stat['size'] = 0;
0334         $stat['atime'] = 0;
0335         $stat['mtime'] = 0;
0336         $stat['ctime'] = 0;
0337         $stat['blksize'] = 0;
0338         $stat['blocks'] = 0;
0339 
0340     if(($slash = strchr($this->_objectName, '/')) === false || $slash == strlen($this->_objectName)-1) {
0341         /* bucket */
0342         $stat['mode'] |= 040000;
0343     } else {
0344         $stat['mode'] |= 0100000;
0345     }
0346            $info = $this->_s3->getInfo($this->_objectName);
0347         if (!empty($info)) {
0348             $stat['size']  = $info['size'];
0349             $stat['atime'] = time();
0350             $stat['mtime'] = $info['mtime'];
0351         }
0352 
0353         return $stat;
0354     }
0355 
0356     /**
0357      * Attempt to delete the item
0358      *
0359      * @param  string $path
0360      * @return boolean
0361      */
0362     public function unlink($path)
0363     {
0364         return $this->_getS3Client($path)->removeObject($this->_getNamePart($path));
0365     }
0366 
0367     /**
0368      * Attempt to rename the item
0369      *
0370      * @param  string  $path_from
0371      * @param  string  $path_to
0372      * @return boolean False
0373      */
0374     public function rename($path_from, $path_to)
0375     {
0376         // TODO: Renaming isn't supported, always return false
0377         return false;
0378     }
0379 
0380     /**
0381      * Create a new directory
0382      *
0383      * @param  string  $path
0384      * @param  integer $mode
0385      * @param  integer $options
0386      * @return boolean
0387      */
0388     public function mkdir($path, $mode, $options)
0389     {
0390         return $this->_getS3Client($path)->createBucket(parse_url($path, PHP_URL_HOST));
0391     }
0392 
0393     /**
0394      * Remove a directory
0395      *
0396      * @param  string  $path
0397      * @param  integer $options
0398      * @return boolean
0399      */
0400     public function rmdir($path, $options)
0401     {
0402         return $this->_getS3Client($path)->removeBucket(parse_url($path, PHP_URL_HOST));
0403     }
0404 
0405     /**
0406      * Attempt to open a directory
0407      *
0408      * @param  string $path
0409      * @param  integer $options
0410      * @return boolean
0411      */
0412     public function dir_opendir($path, $options)
0413     {
0414 
0415         if (preg_match('@^([a-z0-9+.]|-)+://$@', $path)) {
0416             $this->_bucketList = $this->_getS3Client($path)->getBuckets();
0417         }
0418         else {
0419             $host = parse_url($path, PHP_URL_HOST);
0420             $this->_bucketList = $this->_getS3Client($path)->getObjectsByBucket($host);
0421         }
0422 
0423         return ($this->_bucketList !== false);
0424     }
0425 
0426     /**
0427      * Return array of URL variables
0428      *
0429      * @param  string $path
0430      * @param  integer $flags
0431      * @return array
0432      */
0433     public function url_stat($path, $flags)
0434     {
0435         $stat = array();
0436         $stat['dev'] = 0;
0437         $stat['ino'] = 0;
0438         $stat['mode'] = 0777;
0439         $stat['nlink'] = 0;
0440         $stat['uid'] = 0;
0441         $stat['gid'] = 0;
0442         $stat['rdev'] = 0;
0443         $stat['size'] = 0;
0444         $stat['atime'] = 0;
0445         $stat['mtime'] = 0;
0446         $stat['ctime'] = 0;
0447         $stat['blksize'] = 0;
0448         $stat['blocks'] = 0;
0449 
0450     $name = $this->_getNamePart($path);
0451     if(($slash = strchr($name, '/')) === false || $slash == strlen($name)-1) {
0452         /* bucket */
0453         $stat['mode'] |= 040000;
0454     } else {
0455         $stat['mode'] |= 0100000;
0456     }
0457            $info = $this->_getS3Client($path)->getInfo($name);
0458 
0459         if (!empty($info)) {
0460             $stat['size']  = $info['size'];
0461             $stat['atime'] = time();
0462             $stat['mtime'] = $info['mtime'];
0463         }
0464 
0465         return $stat;
0466     }
0467 
0468     /**
0469      * Return the next filename in the directory
0470      *
0471      * @return string
0472      */
0473     public function dir_readdir()
0474     {
0475         $object = current($this->_bucketList);
0476         if ($object !== false) {
0477             next($this->_bucketList);
0478         }
0479         return $object;
0480     }
0481 
0482     /**
0483      * Reset the directory pointer
0484      *
0485      * @return boolean True
0486      */
0487     public function dir_rewinddir()
0488     {
0489         reset($this->_bucketList);
0490         return true;
0491     }
0492 
0493     /**
0494      * Close a directory
0495      *
0496      * @return boolean True
0497      */
0498     public function dir_closedir()
0499     {
0500         $this->_bucketList = array();
0501         return true;
0502     }
0503 }