elFinderVolumeS3.class.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732
  1. <?php
  2. /**
  3. * @file
  4. *
  5. * elFinder driver for Amazon S3 (SOAP) filesystem.
  6. *
  7. * @author Dmitry (dio) Levashov,
  8. * @author Alexey Sukhotin
  9. * */
  10. class elFinderVolumeS3 extends elFinderVolumeDriver {
  11. protected $driverId = 's3s';
  12. protected $s3;
  13. public function __construct() {
  14. $opts = array(
  15. 'accesskey' => '',
  16. 'secretkey' => '',
  17. 'bucket' => '',
  18. 'tmpPath' => '',
  19. );
  20. $this->options = array_merge($this->options, $opts);
  21. $this->options['mimeDetect'] = 'internal';
  22. }
  23. protected function init() {
  24. if (!$this->options['accesskey']
  25. || !$this->options['secretkey']
  26. || !$this->options['bucket']) {
  27. return $this->setError('Required options undefined.');
  28. }
  29. $this->s3 = new S3SoapClient($this->options['accesskey'], $this->options['secretkey']);
  30. $this->root = $this->options['path'];
  31. $this->rootName = 's3';
  32. return true;
  33. }
  34. protected function configure() {
  35. parent::configure();
  36. if (!empty($this->options['tmpPath'])) {
  37. if ((is_dir($this->options['tmpPath']) || @mkdir($this->options['tmpPath'])) && is_writable($this->options['tmpPath'])) {
  38. $this->tmpPath = $this->options['tmpPath'];
  39. }
  40. }
  41. $this->mimeDetect = 'internal';
  42. }
  43. /**
  44. * Return parent directory path
  45. *
  46. * @param string $path file path
  47. * @return string
  48. * @author Dmitry (dio) Levashov
  49. **/
  50. protected function _dirname($path) {
  51. $newpath = preg_replace("/\/$/", "", $path);
  52. $dn = substr($path, 0, strrpos($newpath, '/')) ;
  53. if (substr($dn, 0, 1) != '/') {
  54. $dn = "/$dn";
  55. }
  56. return $dn;
  57. }
  58. /**
  59. * Return file name
  60. *
  61. * @param string $path file path
  62. * @return string
  63. * @author Dmitry (dio) Levashov
  64. **/
  65. protected function _basename($path) {
  66. return basename($path);
  67. }
  68. /**
  69. * Join dir name and file name and return full path.
  70. * Some drivers (db) use int as path - so we give to concat path to driver itself
  71. *
  72. * @param string $dir dir path
  73. * @param string $name file name
  74. * @return string
  75. * @author Dmitry (dio) Levashov
  76. **/
  77. protected function _joinPath($dir, $name) {
  78. return $dir.DIRECTORY_SEPARATOR.$name;
  79. }
  80. /**
  81. * Return normalized path, this works the same as os.path.normpath() in Python
  82. *
  83. * @param string $path path
  84. * @return string
  85. * @author Troex Nevelin
  86. **/
  87. protected function _normpath($path) {
  88. $tmp = preg_replace("/^\//", "", $path);
  89. $tmp = preg_replace("/\/\//", "/", $tmp);
  90. $tmp = preg_replace("/\/$/", "", $tmp);
  91. return $tmp;
  92. }
  93. /**
  94. * Return file path related to root dir
  95. *
  96. * @param string $path file path
  97. * @return string
  98. * @author Dmitry (dio) Levashov
  99. **/
  100. protected function _relpath($path) {
  101. $newpath = $path;
  102. if (substr($path, 0, 1) != '/') {
  103. $newpath = "/$newpath";
  104. }
  105. $newpath = preg_replace("/\/$/", "", $newpath);
  106. $ret = ($newpath == $this->root) ? '' : substr($newpath, strlen($this->root)+1);
  107. return $ret;
  108. }
  109. /**
  110. * Convert path related to root dir into real path
  111. *
  112. * @param string $path file path
  113. * @return string
  114. * @author Dmitry (dio) Levashov
  115. **/
  116. protected function _abspath($path) {
  117. return $path == $this->separator ? $this->root : $this->root.$this->separator.$path;
  118. }
  119. /**
  120. * Return fake path started from root dir
  121. *
  122. * @param string $path file path
  123. * @return string
  124. * @author Dmitry (dio) Levashov
  125. **/
  126. protected function _path($path) {
  127. return $this->rootName.($path == $this->root ? '' : $this->separator.$this->_relpath($path));
  128. }
  129. /**
  130. * Return true if $path is children of $parent
  131. *
  132. * @param string $path path to check
  133. * @param string $parent parent path
  134. * @return bool
  135. * @author Dmitry (dio) Levashov
  136. **/
  137. protected function _inpath($path, $parent) {
  138. return $path == $parent || strpos($path, $parent.'/') === 0;
  139. }
  140. /**
  141. * Converting array of objects with name and value properties to
  142. * array[key] = value
  143. * @param array $metadata source array
  144. * @return array
  145. * @author Alexey Sukhotin
  146. **/
  147. protected function metaobj2array($metadata) {
  148. $arr = array();
  149. if (is_array($metadata)) {
  150. foreach ($metadata as $meta) {
  151. $arr[$meta->Name] = $meta->Value;
  152. }
  153. } else {
  154. $arr[$metadata->Name] = $metadata->Value;
  155. }
  156. return $arr;
  157. }
  158. /**
  159. * Return stat for given path.
  160. * Stat contains following fields:
  161. * - (int) size file size in b. required
  162. * - (int) ts file modification time in unix time. required
  163. * - (string) mime mimetype. required for folders, others - optionally
  164. * - (bool) read read permissions. required
  165. * - (bool) write write permissions. required
  166. * - (bool) locked is object locked. optionally
  167. * - (bool) hidden is object hidden. optionally
  168. * - (string) alias for symlinks - link target path relative to root path. optionally
  169. * - (string) target for symlinks - link target path. optionally
  170. *
  171. * If file does not exists - returns empty array or false.
  172. *
  173. * @param string $path file path
  174. * @return array|false
  175. * @author Dmitry (dio) Levashov,
  176. * @author Alexey Sukhotin
  177. **/
  178. protected function _stat($path) {
  179. $stat = array(
  180. 'size' => 0,
  181. 'ts' => time(),
  182. 'read' => true,
  183. 'write' => true,
  184. 'locked' => false,
  185. 'hidden' => false,
  186. 'mime' => 'directory',
  187. );
  188. if ($this->root == $path) {
  189. return $stat;
  190. }
  191. $np = $this->_normpath($path);
  192. try {
  193. $obj = $this->s3->GetObject(array('Bucket' => $this->options['bucket'], 'Key' => $np , 'GetMetadata' => true, 'InlineData' => false, 'GetData' => false));
  194. } catch (Exception $e) {
  195. }
  196. if (!isset($obj) || ($obj->GetObjectResponse->Status->Code != 200)) {
  197. $np .= '/';
  198. try {
  199. $obj = $this->s3->GetObject(array('Bucket' => $this->options['bucket'], 'Key' => $np , 'GetMetadata' => true, 'InlineData' => false, 'GetData' => false));
  200. } catch (Exception $e) {
  201. }
  202. }
  203. if (!(isset($obj) && $obj->GetObjectResponse->Status->Code == 200)) {
  204. return array();
  205. }
  206. $mime = '';
  207. $metadata = $this->metaobj2array($obj->GetObjectResponse->Metadata);
  208. $mime = $metadata['Content-Type'];
  209. if (!empty($mime)) {
  210. $stat['mime'] = ($mime == 'binary/octet-stream') ? 'directory' : $mime;
  211. }
  212. if (isset($obj->GetObjectResponse->LastModified)) {
  213. $stat['ts'] = strtotime($obj->GetObjectResponse->LastModified);
  214. }
  215. try {
  216. $files = $this->s3->ListBucket(array('Bucket' => $this->options['bucket'], 'Prefix' => $np, 'Delimiter' => '/'))->ListBucketResponse->Contents;
  217. } catch (Exception $e) {
  218. }
  219. if (!is_array($files)) {
  220. $files = array($files);
  221. }
  222. foreach ($files as $file) {
  223. if ($file->Key == $np) {
  224. $stat['size'] = $file->Size;
  225. }
  226. }
  227. return $stat;
  228. }
  229. /***************** file stat ********************/
  230. /**
  231. * Return true if path is dir and has at least one childs directory
  232. *
  233. * @param string $path dir path
  234. * @return bool
  235. * @author Alexey Sukhotin
  236. **/
  237. protected function _subdirs($path) {
  238. $stat = $this->_stat($path);
  239. if ($stat['mime'] == 'directory') {
  240. $files = $this->_scandir($path);
  241. foreach ($files as $file) {
  242. $fstat = $this->_stat($file);
  243. if ($fstat['mime'] == 'directory') {
  244. return true;
  245. }
  246. }
  247. }
  248. return false;
  249. }
  250. /**
  251. * Return object width and height
  252. * Ususaly used for images, but can be realize for video etc...
  253. *
  254. * @param string $path file path
  255. * @param string $mime file mime type
  256. * @return string
  257. * @author Dmitry (dio) Levashov
  258. **/
  259. protected function _dimensions($path, $mime) {
  260. return false;
  261. }
  262. /******************** file/dir content *********************/
  263. /**
  264. * Return files list in directory
  265. *
  266. * @param string $path dir path
  267. * @return array
  268. * @author Dmitry (dio) Levashov,
  269. * @author Alexey Sukhotin
  270. **/
  271. protected function _scandir($path) {
  272. $s3path = preg_replace("/^\//", "", $path) . '/';
  273. $files = $this->s3->ListBucket(array('Bucket' => $this->options['bucket'], 'delimiter' => '/', 'Prefix' => $s3path))->ListBucketResponse->Contents;
  274. $finalfiles = array();
  275. foreach ($files as $file) {
  276. if (preg_match("|^" . $s3path . "[^/]*/?$|", $file->Key)) {
  277. $fname = preg_replace("/\/$/", "", $file->Key);
  278. $fname = $file->Key;
  279. if ($fname != preg_replace("/\/$/", "", $s3path)) {
  280. }
  281. $finalfiles[] = $fname;
  282. }
  283. }
  284. sort($finalfiles);
  285. return $finalfiles;
  286. }
  287. /**
  288. * Return temporary file path for required file
  289. *
  290. * @param string $path file path
  291. * @return string
  292. * @author Dmitry (dio) Levashov
  293. **/
  294. protected function tmpname($path) {
  295. return $this->tmpPath.DIRECTORY_SEPARATOR.md5($path);
  296. }
  297. /**
  298. * Open file and return file pointer
  299. *
  300. * @param string $path file path
  301. * @param bool $write open file for writing
  302. * @return resource|false
  303. * @author Dmitry (dio) Levashov,
  304. * @author Alexey Sukhotin
  305. **/
  306. protected function _fopen($path, $mode="rb") {
  307. $tn = $this->tmpname($path);
  308. $fp = $this->tmbPath
  309. ? @fopen($tn, 'w+')
  310. : @tmpfile();
  311. if ($fp) {
  312. try {
  313. $obj = $this->s3->GetObject(array('Bucket' => $this->options['bucket'], 'Key' => $this->_normpath($path) , 'GetMetadata' => true, 'InlineData' => true, 'GetData' => true));
  314. } catch (Exception $e) {
  315. }
  316. $mime = '';
  317. $metadata = $this->metaobj2array($obj->GetObjectResponse->Metadata);
  318. fwrite($fp, $obj->GetObjectResponse->Data);
  319. rewind($fp);
  320. return $fp;
  321. }
  322. return false;
  323. }
  324. /**
  325. * Close opened file
  326. *
  327. * @param resource $fp file pointer
  328. * @param string $path file path
  329. * @return bool
  330. * @author Dmitry (dio) Levashov
  331. **/
  332. protected function _fclose($fp, $path='') {
  333. @fclose($fp);
  334. if ($path) {
  335. @unlink($this->tmpname($path));
  336. }
  337. }
  338. /******************** file/dir manipulations *************************/
  339. /**
  340. * Create dir and return created dir path or false on failed
  341. *
  342. * @param string $path parent dir path
  343. * @param string $name new directory name
  344. * @return string|bool
  345. * @author Dmitry (dio) Levashov,
  346. * @author Alexey Sukhotin
  347. **/
  348. protected function _mkdir($path, $name) {
  349. $newkey = $this->_normpath($path);
  350. $newkey = preg_replace("/\/$/", "", $newkey);
  351. $newkey = "$newkey/$name/";
  352. try {
  353. $obj = $this->s3->PutObjectInline(array('Bucket' => $this->options['bucket'], 'Key' => $newkey , 'ContentLength' => 0, 'Data' => ''));
  354. } catch (Exception $e) {
  355. }
  356. if (isset($obj)) {
  357. return "$path/$name";
  358. }
  359. return false;
  360. }
  361. /**
  362. * Create file and return it's path or false on failed
  363. *
  364. * @param string $path parent dir path
  365. * @param string $name new file name
  366. * @return string|bool
  367. * @author Dmitry (dio) Levashov,
  368. * @author Alexey Sukhotin
  369. **/
  370. protected function _mkfile($path, $name) {
  371. $newkey = $this->_normpath($path);
  372. $newkey = preg_replace("/\/$/", "", $newkey);
  373. $newkey = "$newkey/$name";
  374. try {
  375. $obj = $this->s3->PutObjectInline(array('Bucket' => $this->options['bucket'], 'Key' => $newkey , 'ContentLength' => 0, 'Data' => '', 'Metadata' => array(array('Name' => 'Content-Type', 'Value' => 'text/plain'))));
  376. } catch (Exception $e) {
  377. }
  378. if (isset($obj)) {
  379. return "$path/$name";
  380. }
  381. return false;
  382. }
  383. /**
  384. * Create symlink
  385. *
  386. * @param string $source file to link to
  387. * @param string $targetDir folder to create link in
  388. * @param string $name symlink name
  389. * @return bool
  390. * @author Dmitry (dio) Levashov
  391. **/
  392. protected function _symlink($source, $targetDir, $name) {
  393. return false;
  394. }
  395. /**
  396. * Copy file into another file (only inside one volume)
  397. *
  398. * @param string $source source file path
  399. * @param string $target target dir path
  400. * @param string $name file name
  401. * @return bool
  402. * @author Dmitry (dio) Levashov
  403. **/
  404. protected function _copy($source, $targetDir, $name) {
  405. return false;
  406. }
  407. /**
  408. * Move file into another parent dir.
  409. * Return new file path or false.
  410. *
  411. * @param string $source source file path
  412. * @param string $target target dir path
  413. * @param string $name file name
  414. * @return string|bool
  415. * @author Dmitry (dio) Levashov
  416. **/
  417. protected function _move($source, $targetDir, $name) {
  418. return false;
  419. }
  420. /**
  421. * Remove file
  422. *
  423. * @param string $path file path
  424. * @return bool
  425. * @author Dmitry (dio) Levashov
  426. **/
  427. protected function _unlink($path) {
  428. $newkey = $this->_normpath($path);
  429. $newkey = preg_replace("/\/$/", "", $newkey);
  430. try {
  431. $obj = $this->s3->DeleteObject(array('Bucket' => $this->options['bucket'], 'Key' => $newkey));
  432. } catch (Exception $e) {
  433. }
  434. /*$fp = fopen('/tmp/eltest.txt','a+');
  435. fwrite($fp, 'key='.$newkey);*/
  436. if (is_object($obj)) {
  437. //fwrite($fp, 'obj='.var_export($obj,true));
  438. if (isset($obj->DeleteObjectResponse->Code)) {
  439. $rc = $obj->DeleteObjectResponse->Code;
  440. if (substr($rc, 0, 1) == '2') {
  441. return true;
  442. }
  443. }
  444. }
  445. //fclose($fp);
  446. return false;
  447. }
  448. /**
  449. * Remove dir
  450. *
  451. * @param string $path dir path
  452. * @return bool
  453. * @author Dmitry (dio) Levashov
  454. **/
  455. protected function _rmdir($path) {
  456. return $this->_unlink($path . '/');
  457. }
  458. /**
  459. * Create new file and write into it from file pointer.
  460. * Return new file path or false on error.
  461. *
  462. * @param resource $fp file pointer
  463. * @param string $dir target dir path
  464. * @param string $name file name
  465. * @return bool|string
  466. * @author Dmitry (dio) Levashov
  467. **/
  468. protected function _save($fp, $dir, $name, $mime, $w, $h) {
  469. return false;
  470. }
  471. /**
  472. * Get file contents
  473. *
  474. * @param string $path file path
  475. * @return string|false
  476. * @author Dmitry (dio) Levashov
  477. **/
  478. protected function _getContents($path) {
  479. return false;
  480. }
  481. /**
  482. * Write a string to a file
  483. *
  484. * @param string $path file path
  485. * @param string $content new file content
  486. * @return bool
  487. * @author Dmitry (dio) Levashov
  488. **/
  489. protected function _filePutContents($path, $content) {
  490. return false;
  491. }
  492. /**
  493. * Extract files from archive
  494. *
  495. * @param string $path file path
  496. * @param array $arc archiver options
  497. * @return bool
  498. * @author Dmitry (dio) Levashov,
  499. * @author Alexey Sukhotin
  500. **/
  501. protected function _extract($path, $arc) {
  502. return false;
  503. }
  504. /**
  505. * Create archive and return its path
  506. *
  507. * @param string $dir target dir
  508. * @param array $files files names list
  509. * @param string $name archive name
  510. * @param array $arc archiver options
  511. * @return string|bool
  512. * @author Dmitry (dio) Levashov,
  513. * @author Alexey Sukhotin
  514. **/
  515. protected function _archive($dir, $files, $name, $arc) {
  516. return false;
  517. }
  518. /**
  519. * Detect available archivers
  520. *
  521. * @return void
  522. * @author Dmitry (dio) Levashov,
  523. * @author Alexey Sukhotin
  524. **/
  525. protected function _checkArchivers() {
  526. }
  527. }
  528. /**
  529. * SoapClient extension with Amazon S3 WSDL and request signing support
  530. *
  531. * @author Alexey Sukhotin
  532. **/
  533. class S3SoapClient extends SoapClient {
  534. private $accesskey = '';
  535. private $secretkey = '';
  536. public $client = NULL;
  537. public function __construct($key = '', $secret = '') {
  538. $this->accesskey = $key;
  539. $this->secretkey = $secret;
  540. parent::__construct('http://s3.amazonaws.com/doc/2006-03-01/AmazonS3.wsdl');
  541. }
  542. /**
  543. * Method call wrapper which adding S3 signature and default arguments to all S3 operations
  544. *
  545. * @author Alexey Sukhotin
  546. **/
  547. public function __call($method, $arguments) {
  548. /* Getting list of S3 web service functions which requires signing */
  549. $funcs = $this->__getFunctions();
  550. $funcnames = array();
  551. foreach ($funcs as $func) {
  552. preg_match("/\S+\s+([^\)]+)\(/", $func, $m);
  553. if (isset($m[1])) {
  554. $funcnames[] = $m[1];
  555. }
  556. }
  557. /* adding signature to arguments */
  558. if (in_array("{$method}", $funcnames)) {
  559. if (is_array($arguments[0])) {
  560. $arguments[0] = array_merge($arguments[0], $this->sign("{$method}"));
  561. } else {
  562. $arguments[0] = $this->sign("{$method}");
  563. }
  564. }
  565. /*$fp = fopen('/tmp/s3debug.txt', 'a+');
  566. fwrite($fp, 'method='."{$method}". ' timestamp='.date('Y-m-d H:i:s').' args='.var_export($arguments,true) . "\n");
  567. fclose($fp);*/
  568. return parent::__call($method, $arguments);
  569. }
  570. /**
  571. * Generating signature and timestamp for specified S3 operation
  572. *
  573. * @param string $operation S3 operation name
  574. * @return array
  575. * @author Alexey Sukhotin
  576. **/
  577. protected function sign($operation) {
  578. $params = array(
  579. 'AWSAccessKeyId' => $this->accesskey,
  580. 'Timestamp' => gmdate('Y-m-d\TH:i:s.000\Z'),
  581. );
  582. $sign_str = 'AmazonS3' . $operation . $params['Timestamp'];
  583. $params['Signature'] = base64_encode(hash_hmac('sha1', $sign_str, $this->secretkey, TRUE));
  584. return $params;
  585. }
  586. }
  587. ?>