├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── data └── mime.json ├── phpunit.xml.dist ├── scripts └── generateMimeJson.php ├── src └── S3StreamWrapper │ ├── MimeType.php │ └── S3StreamWrapper.php └── tests ├── S3StreamWrapper └── Test │ └── InitTest.php ├── bootstrap.php └── config.ini.dist /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | /tests/config.ini 3 | /composer.lock 4 | /.idea/ 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Gijs Kunze 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # S3StreamWrapper # 2 | 3 | A simple stream wrapper for Amazon S3. 4 | 5 | Example 6 | ======= 7 | 8 | ``` php 9 | [ 17 | 'key' => "YOUR AWS KEY HERE", 18 | 'secret' => "YOUR AWS SECRET HERE" 19 | ], 20 | 'region' => 'us-east-1', 21 | 'version' => '2006-03-01' 22 | ); 23 | 24 | stream_context_set_default(array('s3' => $options)); 25 | 26 | echo file_get_contents("s3://mybucket/file1"); 27 | 28 | echo file_put_contents("s3://mybucket/file2", "Foobar!"); 29 | 30 | print_r(scandir("s3://mybucket/")); 31 | ``` 32 | 33 | License 34 | ======= 35 | 36 | MIT, See LICENSE 37 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gwk/s3streamwrapper", 3 | "description": "Amazon AWS S3 Stream Wrapper", 4 | "keywords": ["aws", "s3", "stream", "stream wrapper"], 5 | "require": { 6 | "aws/aws-sdk-php": ">=2.1.0" 7 | }, 8 | "require-dev": { 9 | "phpunit/phpunit": "3.7.*" 10 | }, 11 | "license": "MIT", 12 | "authors": [ 13 | { 14 | "name": "Gijs Kunze", 15 | "email": "gwkunze@gmail.com" 16 | } 17 | ], 18 | "autoload": { 19 | "psr-0": {"S3StreamWrapper": "src/"} 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /data/mime.json: -------------------------------------------------------------------------------- 1 | {"ez":"application\/andrew-inset","anx":"application\/annodex","atom":"application\/atom+xml","atomcat":"application\/atomcat+xml","atomsrv":"application\/atomserv+xml","lin":"application\/bbolin","cap":"application\/cap","pcap":"application\/cap","cu":"application\/cu-seeme","davmount":"application\/davmount+xml","tsp":"application\/dsptype","es":"application\/ecmascript","spl":"application\/x-futuresplash","hta":"application\/hta","jar":"application\/java-archive","ser":"application\/java-serialized-object","class":"application\/java-vm","js":"application\/javascript","json":"application\/json","m3g":"application\/m3g","hqx":"application\/mac-binhex40","cpt":"image\/x-corelphotopaint","nb":"application\/mathematica","nbp":"application\/mathematica","mdb":"application\/msaccess","doc":"application\/msword","dot":"application\/msword","mxf":"application\/mxf","bin":"application\/octet-stream","oda":"application\/oda","ogx":"application\/ogg","one":"application\/onenote","onetoc2":"application\/onenote","onetmp":"application\/onenote","onepkg":"application\/onenote","pdf":"application\/pdf","key":"application\/pgp-keys","pgp":"application\/pgp-signature","prf":"application\/pics-rules","ps":"application\/postscript","ai":"application\/postscript","eps":"application\/postscript","epsi":"application\/postscript","epsf":"application\/postscript","eps2":"application\/postscript","eps3":"application\/postscript","rar":"application\/rar","rdf":"application\/rdf+xml","rss":"application\/rss+xml","rtf":"application\/rtf","stl":"application\/sla","smi":"application\/smil","smil":"application\/smil","xhtml":"application\/xhtml+xml","xht":"application\/xhtml+xml","xml":"application\/xml","xsl":"application\/xml","xsd":"application\/xml","xspf":"application\/xspf+xml","zip":"application\/zip","apk":"application\/vnd.android.package-archive","cdy":"application\/vnd.cinderella","kml":"application\/vnd.google-earth.kml+xml","kmz":"application\/vnd.google-earth.kmz","xul":"application\/vnd.mozilla.xul+xml","xls":"application\/vnd.ms-excel","xlb":"application\/vnd.ms-excel","xlt":"application\/vnd.ms-excel","xlam":"application\/vnd.ms-excel.addin.macroEnabled.12","xlsb":"application\/vnd.ms-excel.sheet.binary.macroEnabled.12","xlsm":"application\/vnd.ms-excel.sheet.macroEnabled.12","xltm":"application\/vnd.ms-excel.template.macroEnabled.12","thmx":"application\/vnd.ms-officetheme","cat":"application\/vnd.ms-pki.seccat","ppt":"application\/vnd.ms-powerpoint","pps":"application\/vnd.ms-powerpoint","ppam":"application\/vnd.ms-powerpoint.addin.macroEnabled.12","pptm":"application\/vnd.ms-powerpoint.presentation.macroEnabled.12","sldm":"application\/vnd.ms-powerpoint.slide.macroEnabled.12","ppsm":"application\/vnd.ms-powerpoint.slideshow.macroEnabled.12","potm":"application\/vnd.ms-powerpoint.template.macroEnabled.12","docm":"application\/vnd.ms-word.document.macroEnabled.12","dotm":"application\/vnd.ms-word.template.macroEnabled.12","odc":"application\/vnd.oasis.opendocument.chart","odb":"application\/vnd.oasis.opendocument.database","odf":"application\/vnd.oasis.opendocument.formula","odg":"application\/vnd.oasis.opendocument.graphics","otg":"application\/vnd.oasis.opendocument.graphics-template","odi":"application\/vnd.oasis.opendocument.image","odp":"application\/vnd.oasis.opendocument.presentation","otp":"application\/vnd.oasis.opendocument.presentation-template","ods":"application\/vnd.oasis.opendocument.spreadsheet","ots":"application\/vnd.oasis.opendocument.spreadsheet-template","odt":"application\/vnd.oasis.opendocument.text","odm":"application\/vnd.oasis.opendocument.text-master","ott":"application\/vnd.oasis.opendocument.text-template","oth":"application\/vnd.oasis.opendocument.text-web","pptx":"application\/vnd.openxmlformats-officedocument.presentationml.presentation","sldx":"application\/vnd.openxmlformats-officedocument.presentationml.slide","ppsx":"application\/vnd.openxmlformats-officedocument.presentationml.slideshow","potx":"application\/vnd.openxmlformats-officedocument.presentationml.template","xlsx":"application\/vnd.openxmlformats-officedocument.spreadsheetml.sheet","xltx":"application\/vnd.openxmlformats-officedocument.spreadsheetml.template","docx":"application\/vnd.openxmlformats-officedocument.wordprocessingml.document","dotx":"application\/vnd.openxmlformats-officedocument.wordprocessingml.template","cod":"application\/vnd.rim.cod","mmf":"application\/vnd.smaf","sdc":"application\/vnd.stardivision.calc","sds":"application\/vnd.stardivision.chart","sda":"application\/vnd.stardivision.draw","sdd":"application\/vnd.stardivision.impress","sdf":"chemical\/x-mdl-sdfile","sdw":"application\/vnd.stardivision.writer","sgl":"application\/vnd.stardivision.writer-global","sxc":"application\/vnd.sun.xml.calc","stc":"application\/vnd.sun.xml.calc.template","sxd":"application\/vnd.sun.xml.draw","std":"application\/vnd.sun.xml.draw.template","sxi":"application\/vnd.sun.xml.impress","sti":"application\/vnd.sun.xml.impress.template","sxm":"application\/vnd.sun.xml.math","sxw":"application\/vnd.sun.xml.writer","sxg":"application\/vnd.sun.xml.writer.global","stw":"application\/vnd.sun.xml.writer.template","sis":"application\/vnd.symbian.install","vsd":"application\/vnd.visio","wbxml":"application\/vnd.wap.wbxml","wmlc":"application\/vnd.wap.wmlc","wmlsc":"application\/vnd.wap.wmlscriptc","wpd":"application\/vnd.wordperfect","wp5":"application\/vnd.wordperfect5.1","wk":"application\/x-123","7z":"application\/x-7z-compressed","abw":"application\/x-abiword","dmg":"application\/x-apple-diskimage","bcpio":"application\/x-bcpio","torrent":"application\/x-bittorrent","cab":"application\/x-cab","cbr":"application\/x-cbr","cbz":"application\/x-cbz","cdf":"application\/x-cdf","cda":"application\/x-cdf","vcd":"application\/x-cdlink","pgn":"application\/x-chess-pgn","mph":"application\/x-comsol","cpio":"application\/x-cpio","csh":"text\/x-csh","deb":"application\/x-debian-package","udeb":"application\/x-debian-package","dcr":"application\/x-director","dir":"application\/x-director","dxr":"application\/x-director","dms":"application\/x-dms","wad":"application\/x-doom","dvi":"application\/x-dvi","mm":"application\/x-freemind","gan":"application\/x-ganttproject","gnumeric":"application\/x-gnumeric","sgf":"application\/x-go-sgf","gcf":"application\/x-graphing-calculator","gtar":"application\/x-gtar","tgz":"application\/x-gtar-compressed","taz":"application\/x-gtar-compressed","hdf":"application\/x-hdf","rhtml":"application\/x-httpd-eruby","phtml":"application\/x-httpd-php","pht":"application\/x-httpd-php","php":"application\/x-httpd-php","phps":"application\/x-httpd-php-source","php3":"application\/x-httpd-php3","php3p":"application\/x-httpd-php3-preprocessed","php4":"application\/x-httpd-php4","php5":"application\/x-httpd-php5","ica":"application\/x-ica","info":"application\/x-info","ins":"application\/x-internet-signup","isp":"application\/x-internet-signup","iii":"application\/x-iphone","iso":"application\/x-iso9660-image","jam":"application\/x-jam","jnlp":"application\/x-java-jnlp-file","jmz":"application\/x-jmol","chrt":"application\/x-kchart","kil":"application\/x-killustrator","skp":"application\/x-koan","skd":"application\/x-koan","skt":"application\/x-koan","skm":"application\/x-koan","kpr":"application\/x-kpresenter","kpt":"application\/x-kpresenter","ksp":"application\/x-kspread","kwd":"application\/x-kword","kwt":"application\/x-kword","latex":"application\/x-latex","lha":"application\/x-lha","lyx":"application\/x-lyx","lzh":"application\/x-lzh","lzx":"application\/x-lzx","frm":"application\/x-maker","maker":"application\/x-maker","frame":"application\/x-maker","fm":"application\/x-maker","fb":"application\/x-maker","book":"application\/x-maker","fbdoc":"application\/x-maker","mif":"application\/x-mif","m3u8":"application\/x-mpegURL","wmd":"application\/x-ms-wmd","wmz":"application\/x-ms-wmz","com":"application\/x-msdos-program","exe":"application\/x-msdos-program","bat":"application\/x-msdos-program","dll":"application\/x-msdos-program","msi":"application\/x-msi","nc":"application\/x-netcdf","pac":"application\/x-ns-proxy-autoconfig","dat":"application\/x-ns-proxy-autoconfig","nwc":"application\/x-nwc","o":"application\/x-object","oza":"application\/x-oz-application","p7r":"application\/x-pkcs7-certreqresp","crl":"application\/x-pkcs7-crl","pyc":"application\/x-python-code","pyo":"application\/x-python-code","qgs":"application\/x-qgis","shp":"application\/x-qgis","shx":"application\/x-qgis","qtl":"application\/x-quicktimeplayer","rdp":"application\/x-rdp","rpm":"application\/x-redhat-package-manager","rb":"application\/x-ruby","sci":"application\/x-scilab","sce":"application\/x-scilab","sh":"text\/x-sh","shar":"application\/x-shar","swf":"application\/x-shockwave-flash","swfl":"application\/x-shockwave-flash","scr":"application\/x-silverlight","sql":"application\/x-sql","sit":"application\/x-stuffit","sitx":"application\/x-stuffit","sv4cpio":"application\/x-sv4cpio","sv4crc":"application\/x-sv4crc","tar":"application\/x-tar","tcl":"text\/x-tcl","gf":"application\/x-tex-gf","pk":"application\/x-tex-pk","texinfo":"application\/x-texinfo","texi":"application\/x-texinfo","t":"application\/x-troff","tr":"application\/x-troff","roff":"application\/x-troff","man":"application\/x-troff-man","me":"application\/x-troff-me","ms":"application\/x-troff-ms","ustar":"application\/x-ustar","src":"application\/x-wais-source","wz":"application\/x-wingz","crt":"application\/x-x509-ca-cert","xcf":"application\/x-xcf","fig":"application\/x-xfig","xpi":"application\/x-xpinstall","amr":"audio\/amr","awb":"audio\/amr-wb","axa":"audio\/annodex","au":"audio\/basic","snd":"audio\/basic","csd":"audio\/csound","orc":"audio\/csound","sco":"audio\/csound","flac":"audio\/flac","mid":"audio\/midi","midi":"audio\/midi","kar":"audio\/midi","mpga":"audio\/mpeg","mpega":"audio\/mpeg","mp2":"audio\/mpeg","mp3":"audio\/mpeg","m4a":"audio\/mpeg","m3u":"audio\/x-mpegurl","oga":"audio\/ogg","ogg":"audio\/ogg","spx":"audio\/ogg","sid":"audio\/prs.sid","aif":"audio\/x-aiff","aiff":"audio\/x-aiff","aifc":"audio\/x-aiff","gsm":"audio\/x-gsm","wma":"audio\/x-ms-wma","wax":"audio\/x-ms-wax","ra":"audio\/x-realaudio","rm":"audio\/x-pn-realaudio","ram":"audio\/x-pn-realaudio","pls":"audio\/x-scpls","sd2":"audio\/x-sd2","wav":"audio\/x-wav","alc":"chemical\/x-alchemy","cac":"chemical\/x-cache","cache":"chemical\/x-cache","csf":"chemical\/x-cache-csf","cbin":"chemical\/x-cactvs-binary","cascii":"chemical\/x-cactvs-binary","ctab":"chemical\/x-cactvs-binary","cdx":"chemical\/x-cdx","cer":"chemical\/x-cerius","c3d":"chemical\/x-chem3d","chm":"chemical\/x-chemdraw","cif":"chemical\/x-cif","cmdf":"chemical\/x-cmdf","cml":"chemical\/x-cml","cpa":"chemical\/x-compass","bsd":"chemical\/x-crossfire","csml":"chemical\/x-csml","csm":"chemical\/x-csml","ctx":"chemical\/x-ctx","cxf":"chemical\/x-cxf","cef":"chemical\/x-cxf","emb":"chemical\/x-embl-dl-nucleotide","embl":"chemical\/x-embl-dl-nucleotide","spc":"chemical\/x-galactic-spc","inp":"chemical\/x-gamess-input","gam":"chemical\/x-gamess-input","gamin":"chemical\/x-gamess-input","fch":"chemical\/x-gaussian-checkpoint","fchk":"chemical\/x-gaussian-checkpoint","cub":"chemical\/x-gaussian-cube","gau":"chemical\/x-gaussian-input","gjc":"chemical\/x-gaussian-input","gjf":"chemical\/x-gaussian-input","gal":"chemical\/x-gaussian-log","gcg":"chemical\/x-gcg8-sequence","gen":"chemical\/x-genbank","hin":"chemical\/x-hin","istr":"chemical\/x-isostar","ist":"chemical\/x-isostar","jdx":"chemical\/x-jcamp-dx","dx":"chemical\/x-jcamp-dx","kin":"chemical\/x-kinemage","mcm":"chemical\/x-macmolecule","mmd":"chemical\/x-macromodel-input","mmod":"chemical\/x-macromodel-input","mol":"chemical\/x-mdl-molfile","rd":"chemical\/x-mdl-rdfile","rxn":"chemical\/x-mdl-rxnfile","sd":"chemical\/x-mdl-sdfile","tgf":"chemical\/x-mdl-tgf","mcif":"chemical\/x-mmcif","mol2":"chemical\/x-mol2","b":"chemical\/x-molconn-Z","gpt":"chemical\/x-mopac-graph","mop":"chemical\/x-mopac-input","mopcrt":"chemical\/x-mopac-input","mpc":"chemical\/x-mopac-input","zmt":"chemical\/x-mopac-input","moo":"chemical\/x-mopac-out","mvb":"chemical\/x-mopac-vib","asn":"chemical\/x-ncbi-asn1-spec","prt":"chemical\/x-ncbi-asn1-ascii","ent":"chemical\/x-pdb","val":"chemical\/x-ncbi-asn1-binary","aso":"chemical\/x-ncbi-asn1-binary","pdb":"chemical\/x-pdb","ros":"chemical\/x-rosdal","sw":"chemical\/x-swissprot","vms":"chemical\/x-vamas-iso14976","vmd":"chemical\/x-vmd","xtel":"chemical\/x-xtel","xyz":"chemical\/x-xyz","gif":"image\/gif","ief":"image\/ief","jpeg":"image\/jpeg","jpg":"image\/jpeg","jpe":"image\/jpeg","pcx":"image\/pcx","png":"image\/png","svg":"image\/svg+xml","svgz":"image\/svg+xml","tiff":"image\/tiff","tif":"image\/tiff","djvu":"image\/vnd.djvu","djv":"image\/vnd.djvu","wbmp":"image\/vnd.wap.wbmp","cr2":"image\/x-canon-cr2","crw":"image\/x-canon-crw","ras":"image\/x-cmu-raster","cdr":"image\/x-coreldraw","pat":"image\/x-coreldrawpattern","cdt":"image\/x-coreldrawtemplate","erf":"image\/x-epson-erf","ico":"image\/x-icon","art":"image\/x-jg","jng":"image\/x-jng","bmp":"image\/x-ms-bmp","nef":"image\/x-nikon-nef","orf":"image\/x-olympus-orf","psd":"image\/x-photoshop","pnm":"image\/x-portable-anymap","pbm":"image\/x-portable-bitmap","pgm":"image\/x-portable-graymap","ppm":"image\/x-portable-pixmap","rgb":"image\/x-rgb","xbm":"image\/x-xbitmap","xpm":"image\/x-xpixmap","xwd":"image\/x-xwindowdump","eml":"message\/rfc822","igs":"model\/iges","iges":"model\/iges","msh":"model\/mesh","mesh":"model\/mesh","silo":"model\/mesh","wrl":"x-world\/x-vrml","vrml":"x-world\/x-vrml","x3dv":"model\/x3d+vrml","x3d":"model\/x3d+xml","x3db":"model\/x3d+binary","manifest":"text\/cache-manifest","ics":"text\/calendar","icz":"text\/calendar","css":"text\/css","csv":"text\/csv","323":"text\/h323","html":"text\/html","htm":"text\/html","shtml":"text\/html","uls":"text\/iuls","mml":"text\/mathml","asc":"text\/plain","txt":"text\/plain","text":"text\/plain","pot":"text\/plain","brf":"text\/plain","rtx":"text\/richtext","sct":"text\/scriptlet","wsc":"text\/scriptlet","tm":"text\/texmacs","tsv":"text\/tab-separated-values","jad":"text\/vnd.sun.j2me.app-descriptor","wml":"text\/vnd.wap.wml","wmls":"text\/vnd.wap.wmlscript","bib":"text\/x-bibtex","boo":"text\/x-boo","h":"text\/x-chdr","htc":"text\/x-component","c":"text\/x-csrc","d":"text\/x-dsrc","diff":"text\/x-diff","patch":"text\/x-diff","hs":"text\/x-haskell","java":"text\/x-java","lhs":"text\/x-literate-haskell","moc":"text\/x-moc","p":"text\/x-pascal","pas":"text\/x-pascal","gcd":"text\/x-pcs-gcd","pl":"text\/x-perl","pm":"text\/x-perl","py":"text\/x-python","scala":"text\/x-scala","etx":"text\/x-setext","sfv":"text\/x-sfv","tk":"text\/x-tcl","tex":"text\/x-tex","ltx":"text\/x-tex","sty":"text\/x-tex","cls":"text\/x-tex","vcs":"text\/x-vcalendar","vcf":"text\/x-vcard","3gp":"video\/3gpp","axv":"video\/annodex","dl":"video\/dl","dif":"video\/dv","dv":"video\/dv","fli":"video\/fli","gl":"video\/gl","mpeg":"video\/mpeg","mpg":"video\/mpeg","mpe":"video\/mpeg","ts":"video\/MP2T","mp4":"video\/mp4","qt":"video\/quicktime","mov":"video\/quicktime","ogv":"video\/ogg","webm":"video\/webm","mxu":"video\/vnd.mpegurl","flv":"video\/x-flv","lsf":"video\/x-la-asf","lsx":"video\/x-la-asf","mng":"video\/x-mng","asf":"video\/x-ms-asf","asx":"video\/x-ms-asf","wm":"video\/x-ms-wm","wmv":"video\/x-ms-wmv","wmx":"video\/x-ms-wmx","wvx":"video\/x-ms-wvx","avi":"video\/x-msvideo","movie":"video\/x-sgi-movie","mpv":"video\/x-matroska","mkv":"video\/x-matroska","ice":"x-conference\/x-cooltalk","sisx":"x-epoc\/x-sisx-app","vrm":"x-world\/x-vrml"} -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | ./tests/S3StreamWrapper/Test/ 12 | 13 | 14 | 15 | 16 | 17 | ./src/S3StreamWrapper/ 18 | 19 | 20 | -------------------------------------------------------------------------------- /scripts/generateMimeJson.php: -------------------------------------------------------------------------------- 1 | context; 67 | if ($context === null) { 68 | $context = stream_context_get_default(); 69 | } 70 | $options = stream_context_get_options($context); 71 | return $options['s3']; 72 | } 73 | 74 | private function getSeparator() 75 | { 76 | $options = $this->getOptions(); 77 | if (isset($options['separator'])) { 78 | return $options['separator']; 79 | } 80 | return "/"; 81 | } 82 | 83 | private function getClient() 84 | { 85 | if (empty($this->client)) { 86 | $this->client = call_user_func(array(self::$clientClass, 'factory'), $this->getOptions()); 87 | } 88 | return $this->client; 89 | } 90 | 91 | private function parsePath($path, $dir = false) 92 | { 93 | $parsed = parse_url($path); 94 | 95 | $bucket = $parsed['host']; 96 | $path = isset($parsed['path']) ? ltrim($parsed['path'], '/') : ''; 97 | 98 | if (!$dir) { 99 | $path = rtrim($path, $this->getSeparator()); 100 | } 101 | 102 | return array('bucket' => $bucket, 'path' => $path); 103 | } 104 | 105 | private $dir_list = null; 106 | private $dir_list_options = null; 107 | private $dir_list_marker = null; 108 | private $dir_list_has_more = false; 109 | 110 | /** 111 | * @return bool 112 | */ 113 | public function dir_closedir() 114 | { 115 | $this->dir_list = null; 116 | $this->dir_list_options = null; 117 | $this->dir_list_marker = null; 118 | $this->dir_list_has_more = false; 119 | } 120 | 121 | /** 122 | * @param string $path 123 | * @param int $options 124 | * @return bool 125 | */ 126 | public function dir_opendir($path, $options) 127 | { 128 | $parsed = $this->parsePath($path, true); 129 | 130 | $this->dir_list_options = array( 131 | 'Bucket' => $parsed['bucket'], 132 | 'Delimiter' => $this->getSeparator(), 133 | ); 134 | 135 | if (strlen($parsed['path']) > 0) { 136 | $this->dir_list_options['Prefix'] = rtrim($parsed['path'], $this->getSeparator()) . $this->getSeparator(); 137 | } 138 | 139 | $this->dir_list_has_more = true; 140 | 141 | return true; 142 | } 143 | 144 | /** 145 | * @return string 146 | */ 147 | public function dir_readdir() 148 | { 149 | if (is_array($this->dir_list) && count($this->dir_list) > 0) { 150 | $item = array_shift($this->dir_list); 151 | if(isset($this->dir_list_options["Prefix"])) { 152 | $item = str_replace($this->dir_list_options["Prefix"], "", $item); 153 | } 154 | return $item; 155 | } 156 | 157 | if ($this->dir_list_has_more) { 158 | $client = $this->getClient(); 159 | $options = $this->dir_list_options; 160 | if ($this->dir_list_marker !== null) { 161 | $options['Marker'] = $this->dir_list_marker; 162 | } 163 | /** @var $response Model */ 164 | $response = $client->listObjects($options); 165 | 166 | if ($response->get("IsTruncated")) { 167 | $this->dir_list_has_more = true; 168 | $this->dir_list_marker = $response->get("NextMarker"); 169 | } else { 170 | $this->dir_list_has_more = false; 171 | } 172 | $contents = $response->get("Contents"); 173 | $this->dir_list = array(); 174 | if(is_array($contents)) { 175 | foreach ($contents as $file) { 176 | $this->dir_list[] = $file['Key']; 177 | } 178 | } 179 | $prefixes = $response->get("CommonPrefixes"); 180 | if(is_array($prefixes)) { 181 | foreach ($prefixes as $dir) { 182 | $this->dir_list[] = $dir['Prefix']; 183 | } 184 | } 185 | 186 | return $this->dir_readdir(); 187 | } 188 | 189 | return false; 190 | } 191 | 192 | /** 193 | * @return bool 194 | */ 195 | public function dir_rewinddir() 196 | { 197 | $this->dir_list_has_more = true; 198 | $this->dir_list = array(); 199 | $this->dir_list_marker = null; 200 | } 201 | 202 | /** 203 | * @param string $path 204 | * @param int $mode 205 | * @param int $options 206 | * @return bool 207 | */ 208 | public function mkdir($path, $mode, $options) 209 | { 210 | $parsed = $this->parsePath($path); 211 | 212 | $object = array( 213 | 'Body' => '', 214 | 'Bucket' => $parsed['bucket'], 215 | 'Key' => $parsed['path'] . $this->getSeparator(), 216 | ); 217 | 218 | if (isset($options['acl'])) { 219 | $object['ACL'] = $options['acl']; 220 | } 221 | 222 | $client = $this->getClient(); 223 | $client->putObject($object); 224 | 225 | return true; 226 | } 227 | 228 | /** 229 | * @param string $path_from 230 | * @param string $path_to 231 | * @throws \Exception 232 | * @return bool 233 | */ 234 | public function rename($path_from, $path_to) 235 | { 236 | // Pretty complex 237 | // TODO: Sane implementation 238 | trigger_error("Rename has not been implemented (yet)", E_USER_ERROR); 239 | } 240 | 241 | /** 242 | * @param string $path 243 | * @param int $options 244 | * @return bool 245 | */ 246 | public function rmdir($path, $options) 247 | { 248 | $parsed = $this->parsePath($path); 249 | 250 | $client = $this->getClient(); 251 | 252 | try { 253 | $result = $client->headObject(array( 254 | 'Bucket' => $parsed['bucket'], 255 | 'Key' => $parsed['path'] . $this->getSeparator(), 256 | )); 257 | } catch (NoSuchKeyException $e) { 258 | return true; 259 | } 260 | 261 | if($result['ContentLength'] != 0) { 262 | return false; 263 | } 264 | 265 | $client->deleteObject(array( 266 | 'Bucket' => $parsed['bucket'], 267 | 'Key' => $parsed['path'] . $this->getSeparator(), 268 | )); 269 | 270 | return true; 271 | } 272 | 273 | /** 274 | * @param int $cast_as 275 | * @return resource 276 | */ 277 | public function stream_cast($cast_as) 278 | { 279 | return false; 280 | } 281 | 282 | /** 283 | * Close the stream 284 | */ 285 | public function stream_close() 286 | { 287 | if ($this->data == null) { 288 | return; 289 | } 290 | 291 | $this->stream_flush(); 292 | 293 | $this->data = null; 294 | $this->path = null; 295 | $this->save = false; 296 | $this->dirty = null; 297 | $this->metadata = array(); 298 | } 299 | 300 | /** 301 | * @return bool 302 | */ 303 | public function stream_eof() 304 | { 305 | if ($this->data === null) { 306 | return true; 307 | } 308 | 309 | return $this->data->ftell() === $this->data->getSize(); 310 | } 311 | 312 | /** 313 | * @return bool 314 | */ 315 | public function stream_flush() 316 | { 317 | if ($this->save === false || $this->dirty == false || $this->data === null) { 318 | return; 319 | } 320 | 321 | $client = $this->getClient(); 322 | 323 | $options = $this->getOptions(); 324 | 325 | $object = array( 326 | 'Bucket' => $this->path['bucket'], 327 | 'Key' => $this->path['path'], 328 | 'Body' => $this->data, 329 | ); 330 | 331 | if (isset($options['acl'])) { 332 | $object['ACL'] = $options['acl']; 333 | } 334 | 335 | if (isset($options['ContentType'])) { 336 | $object['ContentType'] = $options['ContentType']; 337 | } else { 338 | $object['ContentType'] = MimeType::getMimeType($this->path['path'], $this->data); 339 | } 340 | 341 | $headers = array('CacheControl', 'ContentDisposition', 'ContentEncoding', 'ContentLanguage', 'Expires'); 342 | foreach ($headers as $header) { 343 | if (isset($options[$header])) { 344 | $object[$header] = $options[$header]; 345 | } 346 | } 347 | 348 | if (isset($options['metadata'])) { 349 | $object['Metadata'] = $options['metadata']; 350 | } 351 | 352 | $client->putObject($object); 353 | 354 | $this->dirty = false; 355 | } 356 | 357 | /** 358 | * @param string $operation (mode) 359 | * @return bool 360 | */ 361 | public function stream_lock($operation) 362 | { 363 | return false; 364 | } 365 | 366 | private $path = null; 367 | /** @var EntityBodyInterface */ 368 | private $data = null; 369 | private $save = false; 370 | private $dirty = false; 371 | private $metadata = array(); 372 | 373 | /** 374 | * @param string $path 375 | * @param string $mode 376 | * @param int $options 377 | * @param string $opened_path 378 | * @throws \Exception 379 | * @return bool 380 | */ 381 | public function stream_open($path, $mode, $options, &$opened_path) 382 | { 383 | // Load the file? 384 | $load = false; 385 | // Position of the filepointer? start or end 386 | $pos = 'start'; 387 | // check if file exists, error if not exists 388 | $check = false; 389 | // Allow writing? 390 | $write = false; 391 | 392 | $mode = preg_replace("/b/", "", $mode); 393 | 394 | switch ($mode) { 395 | case "r": 396 | $load = true; 397 | $check = true; 398 | break; 399 | case "r+": 400 | $load = true; 401 | $write = true; 402 | break; 403 | case "w": 404 | case "w+": 405 | $write = true; 406 | break; 407 | case "a": 408 | case "a+": 409 | $load = true; 410 | $write = true; 411 | $pos = "end"; 412 | break; 413 | case "x": 414 | case "x+": 415 | $check = true; 416 | $write = true; 417 | break; 418 | case "c": 419 | case "c+": 420 | trigger_error("Mode 'c' and 'c+' Not Supported in S3 Stream Wrapper (yet?)", E_USER_WARNING); 421 | return false; 422 | default: 423 | trigger_error("Invalid mode $mode", E_USER_WARNING); 424 | return false; 425 | } 426 | 427 | $this->data = null; 428 | $this->save = $write; 429 | $this->dirty = true; 430 | $this->path = $this->parsePath($path); 431 | 432 | $client = $this->getClient(); 433 | 434 | if ($check && !$client->doesObjectExist($this->path['bucket'], $this->path['path'])) { 435 | trigger_error("File $path does not exist, can't open with mode $mode", E_USER_WARNING); 436 | return false; 437 | } 438 | 439 | if ($load) { 440 | $options = array( 441 | 'Bucket' => $this->path['bucket'], 442 | 'Key' => $this->path['path'], 443 | ); 444 | /** @var $response Model */ 445 | $response = $client->getObject($options); 446 | $this->data = $response['Body']; 447 | if ($pos === "end") { 448 | $this->data->seek(0, SEEK_END); 449 | } else { 450 | $this->data->seek(0, SEEK_SET); 451 | } 452 | $this->metadata = $response; 453 | $this->dirty = false; 454 | } else { 455 | $this->data = EntityBody::factory(""); 456 | } 457 | 458 | return true; 459 | } 460 | 461 | /** 462 | * @param int $count 463 | * @return string 464 | */ 465 | public function stream_read($count) 466 | { 467 | if ($this->data === null) { 468 | return false; 469 | } 470 | return $this->data->read($count); 471 | } 472 | 473 | /** 474 | * @param int $offset 475 | * @param int $whence 476 | * @return bool 477 | */ 478 | public function stream_seek($offset, $whence = SEEK_SET) 479 | { 480 | if ($this->data === null) { 481 | return false; 482 | } 483 | 484 | return $this->data->seek($offset, $whence); 485 | } 486 | 487 | /** 488 | * @param int $option 489 | * @param int $arg1 490 | * @param int $arg2 491 | * @return bool 492 | */ 493 | public function stream_set_option($option, $arg1, $arg2) 494 | { 495 | return false; 496 | } 497 | 498 | /** 499 | * @return array 500 | */ 501 | public function stream_stat() 502 | { 503 | return $this->stat(self::STAT_FILE, $this->data->getSize(), time()); 504 | } 505 | 506 | /** 507 | * @return int 508 | */ 509 | public function stream_tell() 510 | { 511 | if ($this->data === null) { 512 | return 0; 513 | } 514 | return $this->data->ftell(); 515 | } 516 | 517 | /** 518 | * @param int $new_size 519 | * @return bool 520 | */ 521 | public function stream_truncate($new_size) 522 | { 523 | if ($this->data === null) { 524 | return false; 525 | } 526 | $this->data->setSize($new_size); 527 | return true; 528 | } 529 | 530 | /** 531 | * @param string $data 532 | * @return int 533 | */ 534 | public function stream_write($data) 535 | { 536 | if ($this->data === null) { 537 | return 0; 538 | } 539 | $this->dirty = true; 540 | return $this->data->write($data); 541 | } 542 | 543 | /** 544 | * @param string $path 545 | * @return bool 546 | */ 547 | public function unlink($path) 548 | { 549 | $parsed = $this->parsePath($path); 550 | 551 | $client = $this->getClient(); 552 | 553 | $options = array( 554 | 'Bucket' => $parsed['bucket'], 555 | 'Key' => $parsed['path'], 556 | ); 557 | 558 | $client->deleteObject($options); 559 | 560 | return true; 561 | } 562 | 563 | /** 564 | * @param string $path 565 | * @param int $flags 566 | * @return array 567 | */ 568 | public function url_stat($path, $flags) 569 | { 570 | $parsed = $this->parsePath($path, true); 571 | 572 | if ($parsed['path'] == "") { 573 | // Root is a directory 574 | return $this->stat(self::STAT_DIR, 0, time()); 575 | } 576 | 577 | $options = array( 578 | 'Bucket' => $parsed['bucket'], 579 | 'Key' => $parsed['path'], 580 | ); 581 | 582 | $client = $this->getClient(); 583 | 584 | try { 585 | /** @var $response Model */ 586 | $response = $client->headObject($options); 587 | // Path points to a file 588 | if ($parsed['path'][strlen($parsed['path']) - 1] == $this->getSeparator() && $response['ContentLength'] == 0) { 589 | return $this->stat(self::STAT_DIR, 0, strtotime($response['LastModified'])); 590 | } 591 | return $this->stat(self::STAT_FILE, (int)$response['ContentLength'], strtotime($response['LastModified'])); 592 | } catch (NoSuchKeyException $e) { 593 | // File not found, might be a directory 594 | $options = array( 595 | 'Bucket' => $parsed['bucket'], 596 | 'Prefix' => rtrim($parsed['path'], $this->getSeparator()) . $this->getSeparator(), 597 | 'MaxKeys' => 1, 598 | ); 599 | 600 | $result = $client->listObjects($options); 601 | if (count($result['Contents']) + count($result['CommonPrefixes'])) { 602 | return $this->stat(self::STAT_DIR, 0, time()); 603 | } 604 | 605 | return false; 606 | } 607 | } 608 | 609 | private function stat($permission, $size, $mtime) 610 | { 611 | $data = array( 612 | 'dev' => 0, 613 | 'ino' => 0, 614 | 'mode' => $permission, 615 | 'nlink' => 1, 616 | 'uid' => 0, 617 | 'gid' => 0, 618 | 'rdev' => 0, 619 | 'size' => $size, 620 | 'atime' => $mtime, 621 | 'mtime' => $mtime, 622 | 'ctime' => $mtime, 623 | 'blksize' => -1, 624 | 'blocks' => -1, 625 | ); 626 | 627 | $result = array(); 628 | foreach ($data as $key => $value) { 629 | $result[] = $value; 630 | } 631 | foreach ($data as $key => $value) { 632 | $result[$key] = $value; 633 | } 634 | 635 | return $result; 636 | } 637 | } 638 | -------------------------------------------------------------------------------- /tests/S3StreamWrapper/Test/InitTest.php: -------------------------------------------------------------------------------- 1 | $options)); 24 | } 25 | 26 | protected function tearDown() 27 | { 28 | S3StreamWrapper::unregister(); 29 | } 30 | 31 | 32 | public function testRegister() { 33 | $this->assertTrue(in_array("s3", stream_get_wrappers()), "S3 Stream Wrapper is registered"); 34 | } 35 | 36 | public function testUnregister() { 37 | $this->assertTrue(in_array("s3", stream_get_wrappers()), "S3 Stream Wrapper is registered"); 38 | S3StreamWrapper::unregister(); 39 | $this->assertFalse(in_array("s3", stream_get_wrappers()), "S3 Stream Wrapper is unregistered"); 40 | } 41 | 42 | public function testSetCustomClass() { 43 | $this->assertEquals("Aws\\S3\\S3Client", S3StreamWrapper::getClientClass()); 44 | 45 | S3StreamWrapper::setClientClass("Foo\\Bar"); 46 | 47 | $this->assertEquals("Foo\\Bar", S3StreamWrapper::getClientClass()); 48 | 49 | S3StreamWrapper::setClientClass(); 50 | 51 | $this->assertEquals("Aws\\S3\\S3Client", S3StreamWrapper::getClientClass()); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | add('S3StreamWrapper\\', __DIR__); 10 | 11 | $config_file = __DIR__ . "/config.ini"; 12 | if(!file_exists($config_file)) { 13 | $config_file = __DIR__ . "/config.ini.dist"; 14 | } 15 | $GLOBALS['S3_TESTDATA'] = parse_ini_file($config_file); -------------------------------------------------------------------------------- /tests/config.ini.dist: -------------------------------------------------------------------------------- 1 | [s3] 2 | key= 3 | secret= 4 | bucket= 5 | region=us-east-1 --------------------------------------------------------------------------------