您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154
  1. <?php
  2. /**
  3. * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com>
  4. * This file is licensed under the Affero General Public License version 3 or
  5. * later.
  6. * See the COPYING-README file.
  7. */
  8. /**
  9. * Class to provide access to ownCloud filesystem via a "view", and methods for
  10. * working with files within that view (e.g. read, write, delete, etc.). Each
  11. * view is restricted to a set of directories via a virtual root. The default view
  12. * uses the currently logged in user's data directory as root (parts of
  13. * OC_Filesystem are merely a wrapper for OC\Files\View).
  14. *
  15. * Apps that need to access files outside of the user data folders (to modify files
  16. * belonging to a user other than the one currently logged in, for example) should
  17. * use this class directly rather than using OC_Filesystem, or making use of PHP's
  18. * built-in file manipulation functions. This will ensure all hooks and proxies
  19. * are triggered correctly.
  20. *
  21. * Filesystem functions are not called directly; they are passed to the correct
  22. * \OC\Files\Storage\Storage object
  23. */
  24. namespace OC\Files;
  25. use OC\Files\Cache\Updater;
  26. class View {
  27. private $fakeRoot = '';
  28. public function __construct($root = '') {
  29. $this->fakeRoot = $root;
  30. }
  31. public function getAbsolutePath($path = '/') {
  32. $this->assertPathLength($path);
  33. if ($path === '') {
  34. $path = '/';
  35. }
  36. if ($path[0] !== '/') {
  37. $path = '/' . $path;
  38. }
  39. return $this->fakeRoot . $path;
  40. }
  41. /**
  42. * change the root to a fake root
  43. *
  44. * @param string $fakeRoot
  45. * @return boolean|null
  46. */
  47. public function chroot($fakeRoot) {
  48. if (!$fakeRoot == '') {
  49. if ($fakeRoot[0] !== '/') {
  50. $fakeRoot = '/' . $fakeRoot;
  51. }
  52. }
  53. $this->fakeRoot = $fakeRoot;
  54. }
  55. /**
  56. * get the fake root
  57. *
  58. * @return string
  59. */
  60. public function getRoot() {
  61. return $this->fakeRoot;
  62. }
  63. /**
  64. * get path relative to the root of the view
  65. *
  66. * @param string $path
  67. * @return string
  68. */
  69. public function getRelativePath($path) {
  70. $this->assertPathLength($path);
  71. if ($this->fakeRoot == '') {
  72. return $path;
  73. }
  74. if (strpos($path, $this->fakeRoot) !== 0) {
  75. return null;
  76. } else {
  77. $path = substr($path, strlen($this->fakeRoot));
  78. if (strlen($path) === 0) {
  79. return '/';
  80. } else {
  81. return $path;
  82. }
  83. }
  84. }
  85. /**
  86. * get the mountpoint of the storage object for a path
  87. * ( note: because a storage is not always mounted inside the fakeroot, the
  88. * returned mountpoint is relative to the absolute root of the filesystem
  89. * and doesn't take the chroot into account )
  90. *
  91. * @param string $path
  92. * @return string
  93. */
  94. public function getMountPoint($path) {
  95. return Filesystem::getMountPoint($this->getAbsolutePath($path));
  96. }
  97. /**
  98. * resolve a path to a storage and internal path
  99. *
  100. * @param string $path
  101. * @return array an array consisting of the storage and the internal path
  102. */
  103. public function resolvePath($path) {
  104. $a = $this->getAbsolutePath($path);
  105. $p = Filesystem::normalizePath($a);
  106. return Filesystem::resolvePath($p);
  107. }
  108. /**
  109. * return the path to a local version of the file
  110. * we need this because we can't know if a file is stored local or not from
  111. * outside the filestorage and for some purposes a local file is needed
  112. *
  113. * @param string $path
  114. * @return string
  115. */
  116. public function getLocalFile($path) {
  117. $parent = substr($path, 0, strrpos($path, '/'));
  118. $path = $this->getAbsolutePath($path);
  119. list($storage, $internalPath) = Filesystem::resolvePath($path);
  120. if (Filesystem::isValidPath($parent) and $storage) {
  121. return $storage->getLocalFile($internalPath);
  122. } else {
  123. return null;
  124. }
  125. }
  126. /**
  127. * @param string $path
  128. * @return string
  129. */
  130. public function getLocalFolder($path) {
  131. $parent = substr($path, 0, strrpos($path, '/'));
  132. $path = $this->getAbsolutePath($path);
  133. list($storage, $internalPath) = Filesystem::resolvePath($path);
  134. if (Filesystem::isValidPath($parent) and $storage) {
  135. return $storage->getLocalFolder($internalPath);
  136. } else {
  137. return null;
  138. }
  139. }
  140. /**
  141. * the following functions operate with arguments and return values identical
  142. * to those of their PHP built-in equivalents. Mostly they are merely wrappers
  143. * for \OC\Files\Storage\Storage via basicOperation().
  144. */
  145. public function mkdir($path) {
  146. return $this->basicOperation('mkdir', $path, array('create', 'write'));
  147. }
  148. public function rmdir($path) {
  149. if ($this->is_dir($path)) {
  150. return $this->basicOperation('rmdir', $path, array('delete'));
  151. } else {
  152. return false;
  153. }
  154. }
  155. /**
  156. * @param string $path
  157. * @return resource
  158. */
  159. public function opendir($path) {
  160. return $this->basicOperation('opendir', $path, array('read'));
  161. }
  162. public function readdir($handle) {
  163. $fsLocal = new Storage\Local(array('datadir' => '/'));
  164. return $fsLocal->readdir($handle);
  165. }
  166. public function is_dir($path) {
  167. if ($path == '/') {
  168. return true;
  169. }
  170. return $this->basicOperation('is_dir', $path);
  171. }
  172. public function is_file($path) {
  173. if ($path == '/') {
  174. return false;
  175. }
  176. return $this->basicOperation('is_file', $path);
  177. }
  178. public function stat($path) {
  179. return $this->basicOperation('stat', $path);
  180. }
  181. public function filetype($path) {
  182. return $this->basicOperation('filetype', $path);
  183. }
  184. public function filesize($path) {
  185. return $this->basicOperation('filesize', $path);
  186. }
  187. public function readfile($path) {
  188. $this->assertPathLength($path);
  189. @ob_end_clean();
  190. $handle = $this->fopen($path, 'rb');
  191. if ($handle) {
  192. $chunkSize = 8192; // 8 kB chunks
  193. while (!feof($handle)) {
  194. echo fread($handle, $chunkSize);
  195. flush();
  196. }
  197. $size = $this->filesize($path);
  198. return $size;
  199. }
  200. return false;
  201. }
  202. public function isCreatable($path) {
  203. return $this->basicOperation('isCreatable', $path);
  204. }
  205. public function isReadable($path) {
  206. return $this->basicOperation('isReadable', $path);
  207. }
  208. public function isUpdatable($path) {
  209. return $this->basicOperation('isUpdatable', $path);
  210. }
  211. public function isDeletable($path) {
  212. return $this->basicOperation('isDeletable', $path);
  213. }
  214. public function isSharable($path) {
  215. return $this->basicOperation('isSharable', $path);
  216. }
  217. public function file_exists($path) {
  218. if ($path == '/') {
  219. return true;
  220. }
  221. return $this->basicOperation('file_exists', $path);
  222. }
  223. public function filemtime($path) {
  224. return $this->basicOperation('filemtime', $path);
  225. }
  226. public function touch($path, $mtime = null) {
  227. if (!is_null($mtime) and !is_numeric($mtime)) {
  228. $mtime = strtotime($mtime);
  229. }
  230. $hooks = array('touch');
  231. if (!$this->file_exists($path)) {
  232. $hooks[] = 'create';
  233. $hooks[] = 'write';
  234. }
  235. $result = $this->basicOperation('touch', $path, $hooks, $mtime);
  236. if (!$result) { //if native touch fails, we emulate it by changing the mtime in the cache
  237. $this->putFileInfo($path, array('mtime' => $mtime));
  238. }
  239. return true;
  240. }
  241. public function file_get_contents($path) {
  242. return $this->basicOperation('file_get_contents', $path, array('read'));
  243. }
  244. protected function emit_file_hooks_pre($exists, $path, &$run) {
  245. if (!$exists) {
  246. \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_create, array(
  247. Filesystem::signal_param_path => $this->getHookPath($path),
  248. Filesystem::signal_param_run => &$run,
  249. ));
  250. } else {
  251. \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_update, array(
  252. Filesystem::signal_param_path => $this->getHookPath($path),
  253. Filesystem::signal_param_run => &$run,
  254. ));
  255. }
  256. \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_write, array(
  257. Filesystem::signal_param_path => $this->getHookPath($path),
  258. Filesystem::signal_param_run => &$run,
  259. ));
  260. }
  261. protected function emit_file_hooks_post($exists, $path) {
  262. if (!$exists) {
  263. \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_create, array(
  264. Filesystem::signal_param_path => $this->getHookPath($path),
  265. ));
  266. } else {
  267. \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_update, array(
  268. Filesystem::signal_param_path => $this->getHookPath($path),
  269. ));
  270. }
  271. \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_write, array(
  272. Filesystem::signal_param_path => $this->getHookPath($path),
  273. ));
  274. }
  275. public function file_put_contents($path, $data) {
  276. if (is_resource($data)) { //not having to deal with streams in file_put_contents makes life easier
  277. $absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
  278. if (\OC_FileProxy::runPreProxies('file_put_contents', $absolutePath, $data)
  279. and Filesystem::isValidPath($path)
  280. and !Filesystem::isFileBlacklisted($path)
  281. ) {
  282. $path = $this->getRelativePath($absolutePath);
  283. $exists = $this->file_exists($path);
  284. $run = true;
  285. if ($this->shouldEmitHooks($path)) {
  286. $this->emit_file_hooks_pre($exists, $path, $run);
  287. }
  288. if (!$run) {
  289. return false;
  290. }
  291. $target = $this->fopen($path, 'w');
  292. if ($target) {
  293. list ($count, $result) = \OC_Helper::streamCopy($data, $target);
  294. fclose($target);
  295. fclose($data);
  296. if ($this->shouldEmitHooks($path) && $result !== false) {
  297. Updater::writeHook(array(
  298. 'path' => $this->getHookPath($path)
  299. ));
  300. $this->emit_file_hooks_post($exists, $path);
  301. }
  302. \OC_FileProxy::runPostProxies('file_put_contents', $absolutePath, $count);
  303. return $result;
  304. } else {
  305. return false;
  306. }
  307. } else {
  308. return false;
  309. }
  310. } else {
  311. $hooks = ($this->file_exists($path)) ? array('update', 'write') : array('create', 'write');
  312. return $this->basicOperation('file_put_contents', $path, $hooks, $data);
  313. }
  314. }
  315. public function unlink($path) {
  316. if ($path === '' || $path === '/') {
  317. // do not allow deleting the root
  318. return false;
  319. }
  320. $postFix = (substr($path, -1, 1) === '/') ? '/' : '';
  321. $absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
  322. list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix);
  323. if (!($storage instanceof \OC\Files\Storage\Shared) &&
  324. (!$internalPath || $internalPath === '' || $internalPath === '/')) {
  325. // do not allow deleting the storage's root / the mount point
  326. // because for some storages it might delete the whole contents
  327. // but isn't supposed to work that way
  328. return false;
  329. }
  330. return $this->basicOperation('unlink', $path, array('delete'));
  331. }
  332. /**
  333. * @param string $directory
  334. */
  335. public function deleteAll($directory, $empty = false) {
  336. return $this->rmdir($directory);
  337. }
  338. public function rename($path1, $path2) {
  339. $postFix1 = (substr($path1, -1, 1) === '/') ? '/' : '';
  340. $postFix2 = (substr($path2, -1, 1) === '/') ? '/' : '';
  341. $absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1));
  342. $absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2));
  343. if (
  344. \OC_FileProxy::runPreProxies('rename', $absolutePath1, $absolutePath2)
  345. and Filesystem::isValidPath($path2)
  346. and Filesystem::isValidPath($path1)
  347. and !Filesystem::isFileBlacklisted($path2)
  348. ) {
  349. $path1 = $this->getRelativePath($absolutePath1);
  350. $path2 = $this->getRelativePath($absolutePath2);
  351. $exists = $this->file_exists($path2);
  352. if ($path1 == null or $path2 == null) {
  353. return false;
  354. }
  355. $run = true;
  356. if ($this->shouldEmitHooks() && (Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2))) {
  357. // if it was a rename from a part file to a regular file it was a write and not a rename operation
  358. $this->emit_file_hooks_pre($exists, $path2, $run);
  359. } elseif ($this->shouldEmitHooks()) {
  360. \OC_Hook::emit(
  361. Filesystem::CLASSNAME, Filesystem::signal_rename,
  362. array(
  363. Filesystem::signal_param_oldpath => $this->getHookPath($path1),
  364. Filesystem::signal_param_newpath => $this->getHookPath($path2),
  365. Filesystem::signal_param_run => &$run
  366. )
  367. );
  368. }
  369. if ($run) {
  370. $mp1 = $this->getMountPoint($path1 . $postFix1);
  371. $mp2 = $this->getMountPoint($path2 . $postFix2);
  372. list($storage1, $internalPath1) = Filesystem::resolvePath($absolutePath1 . $postFix1);
  373. list(, $internalPath2) = Filesystem::resolvePath($absolutePath2 . $postFix2);
  374. // if source and target are on the same storage we can call the rename operation from the
  375. // storage. If it is a "Shared" file/folder we call always the rename operation of the
  376. // shared storage to handle mount point renaming, etc correctly
  377. if ($storage1 instanceof \OC\Files\Storage\Shared) {
  378. if ($storage1) {
  379. $result = $storage1->rename($absolutePath1, $absolutePath2);
  380. \OC_FileProxy::runPostProxies('rename', $absolutePath1, $absolutePath2);
  381. } else {
  382. $result = false;
  383. }
  384. } elseif ($mp1 == $mp2) {
  385. if ($storage1) {
  386. $result = $storage1->rename($internalPath1, $internalPath2);
  387. \OC_FileProxy::runPostProxies('rename', $absolutePath1, $absolutePath2);
  388. } else {
  389. $result = false;
  390. }
  391. } else {
  392. if ($this->is_dir($path1)) {
  393. $result = $this->copy($path1, $path2);
  394. if ($result === true) {
  395. $result = $storage1->rmdir($internalPath1);
  396. }
  397. } else {
  398. $source = $this->fopen($path1 . $postFix1, 'r');
  399. $target = $this->fopen($path2 . $postFix2, 'w');
  400. list($count, $result) = \OC_Helper::streamCopy($source, $target);
  401. // close open handle - especially $source is necessary because unlink below will
  402. // throw an exception on windows because the file is locked
  403. fclose($source);
  404. fclose($target);
  405. if ($result !== false) {
  406. $storage1->unlink($internalPath1);
  407. }
  408. }
  409. }
  410. if ($this->shouldEmitHooks() && (Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2)) && $result !== false) {
  411. // if it was a rename from a part file to a regular file it was a write and not a rename operation
  412. Updater::writeHook(array('path' => $this->getHookPath($path2)));
  413. $this->emit_file_hooks_post($exists, $path2);
  414. } elseif ($this->shouldEmitHooks() && $result !== false) {
  415. Updater::renameHook(array(
  416. 'oldpath' => $this->getHookPath($path1),
  417. 'newpath' => $this->getHookPath($path2)
  418. ));
  419. \OC_Hook::emit(
  420. Filesystem::CLASSNAME,
  421. Filesystem::signal_post_rename,
  422. array(
  423. Filesystem::signal_param_oldpath => $this->getHookPath($path1),
  424. Filesystem::signal_param_newpath => $this->getHookPath($path2)
  425. )
  426. );
  427. }
  428. return $result;
  429. } else {
  430. return false;
  431. }
  432. } else {
  433. return false;
  434. }
  435. }
  436. public function copy($path1, $path2) {
  437. $postFix1 = (substr($path1, -1, 1) === '/') ? '/' : '';
  438. $postFix2 = (substr($path2, -1, 1) === '/') ? '/' : '';
  439. $absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1));
  440. $absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2));
  441. if (
  442. \OC_FileProxy::runPreProxies('copy', $absolutePath1, $absolutePath2)
  443. and Filesystem::isValidPath($path2)
  444. and Filesystem::isValidPath($path1)
  445. and !Filesystem::isFileBlacklisted($path2)
  446. ) {
  447. $path1 = $this->getRelativePath($absolutePath1);
  448. $path2 = $this->getRelativePath($absolutePath2);
  449. if ($path1 == null or $path2 == null) {
  450. return false;
  451. }
  452. $run = true;
  453. $exists = $this->file_exists($path2);
  454. if ($this->shouldEmitHooks()) {
  455. \OC_Hook::emit(
  456. Filesystem::CLASSNAME,
  457. Filesystem::signal_copy,
  458. array(
  459. Filesystem::signal_param_oldpath => $this->getHookPath($path1),
  460. Filesystem::signal_param_newpath => $this->getHookPath($path2),
  461. Filesystem::signal_param_run => &$run
  462. )
  463. );
  464. $this->emit_file_hooks_pre($exists, $path2, $run);
  465. }
  466. if ($run) {
  467. $mp1 = $this->getMountPoint($path1 . $postFix1);
  468. $mp2 = $this->getMountPoint($path2 . $postFix2);
  469. if ($mp1 == $mp2) {
  470. list($storage, $internalPath1) = Filesystem::resolvePath($absolutePath1 . $postFix1);
  471. list(, $internalPath2) = Filesystem::resolvePath($absolutePath2 . $postFix2);
  472. if ($storage) {
  473. $result = $storage->copy($internalPath1, $internalPath2);
  474. } else {
  475. $result = false;
  476. }
  477. } else {
  478. if ($this->is_dir($path1) && ($dh = $this->opendir($path1))) {
  479. $result = $this->mkdir($path2);
  480. if (is_resource($dh)) {
  481. while (($file = readdir($dh)) !== false) {
  482. if (!Filesystem::isIgnoredDir($file)) {
  483. $result = $this->copy($path1 . '/' . $file, $path2 . '/' . $file);
  484. }
  485. }
  486. }
  487. } else {
  488. $source = $this->fopen($path1 . $postFix1, 'r');
  489. $target = $this->fopen($path2 . $postFix2, 'w');
  490. list($count, $result) = \OC_Helper::streamCopy($source, $target);
  491. fclose($source);
  492. fclose($target);
  493. }
  494. }
  495. if ($this->shouldEmitHooks() && $result !== false) {
  496. \OC_Hook::emit(
  497. Filesystem::CLASSNAME,
  498. Filesystem::signal_post_copy,
  499. array(
  500. Filesystem::signal_param_oldpath => $this->getHookPath($path1),
  501. Filesystem::signal_param_newpath => $this->getHookPath($path2)
  502. )
  503. );
  504. $this->emit_file_hooks_post($exists, $path2);
  505. }
  506. return $result;
  507. } else {
  508. return false;
  509. }
  510. } else {
  511. return false;
  512. }
  513. }
  514. /**
  515. * @param string $path
  516. * @param string $mode
  517. * @return resource
  518. */
  519. public function fopen($path, $mode) {
  520. $hooks = array();
  521. switch ($mode) {
  522. case 'r':
  523. case 'rb':
  524. $hooks[] = 'read';
  525. break;
  526. case 'r+':
  527. case 'rb+':
  528. case 'w+':
  529. case 'wb+':
  530. case 'x+':
  531. case 'xb+':
  532. case 'a+':
  533. case 'ab+':
  534. $hooks[] = 'read';
  535. $hooks[] = 'write';
  536. break;
  537. case 'w':
  538. case 'wb':
  539. case 'x':
  540. case 'xb':
  541. case 'a':
  542. case 'ab':
  543. $hooks[] = 'write';
  544. break;
  545. default:
  546. \OC_Log::write('core', 'invalid mode (' . $mode . ') for ' . $path, \OC_Log::ERROR);
  547. }
  548. return $this->basicOperation('fopen', $path, $hooks, $mode);
  549. }
  550. public function toTmpFile($path) {
  551. $this->assertPathLength($path);
  552. if (Filesystem::isValidPath($path)) {
  553. $source = $this->fopen($path, 'r');
  554. if ($source) {
  555. $extension = pathinfo($path, PATHINFO_EXTENSION);
  556. $tmpFile = \OC_Helper::tmpFile($extension);
  557. file_put_contents($tmpFile, $source);
  558. return $tmpFile;
  559. } else {
  560. return false;
  561. }
  562. } else {
  563. return false;
  564. }
  565. }
  566. public function fromTmpFile($tmpFile, $path) {
  567. $this->assertPathLength($path);
  568. if (Filesystem::isValidPath($path)) {
  569. // Get directory that the file is going into
  570. $filePath = dirname($path);
  571. // Create the directories if any
  572. if (!$this->file_exists($filePath)) {
  573. $this->mkdir($filePath);
  574. }
  575. if (!$tmpFile) {
  576. debug_print_backtrace();
  577. }
  578. $source = fopen($tmpFile, 'r');
  579. if ($source) {
  580. $this->file_put_contents($path, $source);
  581. unlink($tmpFile);
  582. return true;
  583. } else {
  584. return false;
  585. }
  586. } else {
  587. return false;
  588. }
  589. }
  590. public function getMimeType($path) {
  591. $this->assertPathLength($path);
  592. return $this->basicOperation('getMimeType', $path);
  593. }
  594. public function hash($type, $path, $raw = false) {
  595. $postFix = (substr($path, -1, 1) === '/') ? '/' : '';
  596. $absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
  597. if (\OC_FileProxy::runPreProxies('hash', $absolutePath) && Filesystem::isValidPath($path)) {
  598. $path = $this->getRelativePath($absolutePath);
  599. if ($path == null) {
  600. return false;
  601. }
  602. if ($this->shouldEmitHooks($path)) {
  603. \OC_Hook::emit(
  604. Filesystem::CLASSNAME,
  605. Filesystem::signal_read,
  606. array(Filesystem::signal_param_path => $this->getHookPath($path))
  607. );
  608. }
  609. list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix);
  610. if ($storage) {
  611. $result = $storage->hash($type, $internalPath, $raw);
  612. $result = \OC_FileProxy::runPostProxies('hash', $absolutePath, $result);
  613. return $result;
  614. }
  615. }
  616. return null;
  617. }
  618. public function free_space($path = '/') {
  619. $this->assertPathLength($path);
  620. return $this->basicOperation('free_space', $path);
  621. }
  622. /**
  623. * abstraction layer for basic filesystem functions: wrapper for \OC\Files\Storage\Storage
  624. * @param string $operation
  625. * @param string $path
  626. * @param array $hooks (optional)
  627. * @param mixed $extraParam (optional)
  628. * @return mixed
  629. *
  630. * This method takes requests for basic filesystem functions (e.g. reading & writing
  631. * files), processes hooks and proxies, sanitises paths, and finally passes them on to
  632. * \OC\Files\Storage\Storage for delegation to a storage backend for execution
  633. */
  634. private function basicOperation($operation, $path, $hooks = array(), $extraParam = null) {
  635. $postFix = (substr($path, -1, 1) === '/') ? '/' : '';
  636. $absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
  637. if (\OC_FileProxy::runPreProxies($operation, $absolutePath, $extraParam)
  638. and Filesystem::isValidPath($path)
  639. and !Filesystem::isFileBlacklisted($path)
  640. ) {
  641. $path = $this->getRelativePath($absolutePath);
  642. if ($path == null) {
  643. return false;
  644. }
  645. $run = $this->runHooks($hooks, $path);
  646. list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix);
  647. if ($run and $storage) {
  648. if (!is_null($extraParam)) {
  649. $result = $storage->$operation($internalPath, $extraParam);
  650. } else {
  651. $result = $storage->$operation($internalPath);
  652. }
  653. $result = \OC_FileProxy::runPostProxies($operation, $this->getAbsolutePath($path), $result);
  654. if ($this->shouldEmitHooks($path) && $result !== false) {
  655. if ($operation != 'fopen') { //no post hooks for fopen, the file stream is still open
  656. $this->runHooks($hooks, $path, true);
  657. }
  658. }
  659. return $result;
  660. }
  661. }
  662. return null;
  663. }
  664. /**
  665. * get the path relative to the default root for hook usage
  666. *
  667. * @param string $path
  668. * @return string
  669. */
  670. private function getHookPath($path) {
  671. if (!Filesystem::getView()) {
  672. return $path;
  673. }
  674. return Filesystem::getView()->getRelativePath($this->getAbsolutePath($path));
  675. }
  676. private function shouldEmitHooks($path = '') {
  677. if ($path && Cache\Scanner::isPartialFile($path)) {
  678. return false;
  679. }
  680. if (!Filesystem::$loaded) {
  681. return false;
  682. }
  683. $defaultRoot = Filesystem::getRoot();
  684. if ($this->fakeRoot === $defaultRoot) {
  685. return true;
  686. }
  687. return (strlen($this->fakeRoot) > strlen($defaultRoot)) && (substr($this->fakeRoot, 0, strlen($defaultRoot) + 1) === $defaultRoot . '/');
  688. }
  689. /**
  690. * @param string[] $hooks
  691. * @param string $path
  692. * @param bool $post
  693. * @return bool
  694. */
  695. private function runHooks($hooks, $path, $post = false) {
  696. $path = $this->getHookPath($path);
  697. $prefix = ($post) ? 'post_' : '';
  698. $run = true;
  699. if ($this->shouldEmitHooks($path)) {
  700. foreach ($hooks as $hook) {
  701. // manually triger updater hooks to ensure they are called first
  702. if ($post) {
  703. if ($hook == 'write') {
  704. Updater::writeHook(array('path' => $path));
  705. } elseif ($hook == 'touch') {
  706. Updater::touchHook(array('path' => $path));
  707. } else if ($hook == 'delete') {
  708. Updater::deleteHook(array('path' => $path));
  709. }
  710. }
  711. if ($hook != 'read') {
  712. \OC_Hook::emit(
  713. Filesystem::CLASSNAME,
  714. $prefix . $hook,
  715. array(
  716. Filesystem::signal_param_run => &$run,
  717. Filesystem::signal_param_path => $path
  718. )
  719. );
  720. } elseif (!$post) {
  721. \OC_Hook::emit(
  722. Filesystem::CLASSNAME,
  723. $prefix . $hook,
  724. array(
  725. Filesystem::signal_param_path => $path
  726. )
  727. );
  728. }
  729. }
  730. }
  731. return $run;
  732. }
  733. /**
  734. * check if a file or folder has been updated since $time
  735. *
  736. * @param string $path
  737. * @param int $time
  738. * @return bool
  739. */
  740. public function hasUpdated($path, $time) {
  741. return $this->basicOperation('hasUpdated', $path, array(), $time);
  742. }
  743. /**
  744. * get the filesystem info
  745. *
  746. * @param string $path
  747. * @param boolean $includeMountPoints whether to add mountpoint sizes,
  748. * defaults to true
  749. * @return \OC\Files\FileInfo|false
  750. */
  751. public function getFileInfo($path, $includeMountPoints = true) {
  752. $this->assertPathLength($path);
  753. $data = array();
  754. if (!Filesystem::isValidPath($path)) {
  755. return $data;
  756. }
  757. $path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
  758. /**
  759. * @var \OC\Files\Storage\Storage $storage
  760. * @var string $internalPath
  761. */
  762. list($storage, $internalPath) = Filesystem::resolvePath($path);
  763. $data = null;
  764. if ($storage) {
  765. $cache = $storage->getCache($internalPath);
  766. if (!$cache->inCache($internalPath)) {
  767. if (!$storage->file_exists($internalPath)) {
  768. return false;
  769. }
  770. $scanner = $storage->getScanner($internalPath);
  771. $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
  772. } else {
  773. $watcher = $storage->getWatcher($internalPath);
  774. $data = $watcher->checkUpdate($internalPath);
  775. }
  776. if (!is_array($data)) {
  777. $data = $cache->get($internalPath);
  778. }
  779. if ($data and isset($data['fileid'])) {
  780. if ($includeMountPoints and $data['mimetype'] === 'httpd/unix-directory') {
  781. //add the sizes of other mountpoints to the folder
  782. $mountPoints = Filesystem::getMountPoints($path);
  783. foreach ($mountPoints as $mountPoint) {
  784. $subStorage = Filesystem::getStorage($mountPoint);
  785. if ($subStorage) {
  786. $subCache = $subStorage->getCache('');
  787. $rootEntry = $subCache->get('');
  788. $data['size'] += isset($rootEntry['size']) ? $rootEntry['size'] : 0;
  789. }
  790. }
  791. }
  792. }
  793. }
  794. if (!$data) {
  795. return false;
  796. }
  797. $data = \OC_FileProxy::runPostProxies('getFileInfo', $path, $data);
  798. return new FileInfo($path, $storage, $internalPath, $data);
  799. }
  800. /**
  801. * get the content of a directory
  802. *
  803. * @param string $directory path under datadirectory
  804. * @param string $mimetype_filter limit returned content to this mimetype or mimepart
  805. * @return FileInfo[]
  806. */
  807. public function getDirectoryContent($directory, $mimetype_filter = '') {
  808. $this->assertPathLength($directory);
  809. $result = array();
  810. if (!Filesystem::isValidPath($directory)) {
  811. return $result;
  812. }
  813. $path = Filesystem::normalizePath($this->fakeRoot . '/' . $directory);
  814. /**
  815. * @var \OC\Files\Storage\Storage $storage
  816. * @var string $internalPath
  817. */
  818. list($storage, $internalPath) = Filesystem::resolvePath($path);
  819. if ($storage) {
  820. $cache = $storage->getCache($internalPath);
  821. $user = \OC_User::getUser();
  822. if ($cache->getStatus($internalPath) < Cache\Cache::COMPLETE) {
  823. $scanner = $storage->getScanner($internalPath);
  824. $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
  825. } else {
  826. $watcher = $storage->getWatcher($internalPath);
  827. $watcher->checkUpdate($internalPath);
  828. }
  829. $folderId = $cache->getId($internalPath);
  830. $files = array();
  831. $contents = $cache->getFolderContents($internalPath, $folderId); //TODO: mimetype_filter
  832. foreach ($contents as $content) {
  833. $files[] = new FileInfo($path . '/' . $content['name'], $storage, $content['path'], $content);
  834. }
  835. $ids = array();
  836. foreach ($files as $i => $file) {
  837. $files[$i]['type'] = $file['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file';
  838. $ids[] = $file['fileid'];
  839. if (!isset($permissions[$file['fileid']])) {
  840. $permissions[$file['fileid']] = $storage->getPermissions($file['path']);
  841. }
  842. $files[$i]['permissions'] = $permissions[$file['fileid']];
  843. }
  844. //add a folder for any mountpoint in this directory and add the sizes of other mountpoints to the folders
  845. $mountPoints = Filesystem::getMountPoints($path);
  846. $dirLength = strlen($path);
  847. foreach ($mountPoints as $mountPoint) {
  848. $subStorage = Filesystem::getStorage($mountPoint);
  849. if ($subStorage) {
  850. $subCache = $subStorage->getCache('');
  851. if ($subCache->getStatus('') === Cache\Cache::NOT_FOUND) {
  852. $subScanner = $subStorage->getScanner('');
  853. $subScanner->scanFile('');
  854. }
  855. $rootEntry = $subCache->get('');
  856. if ($rootEntry) {
  857. $relativePath = trim(substr($mountPoint, $dirLength), '/');
  858. if ($pos = strpos($relativePath, '/')) {
  859. //mountpoint inside subfolder add size to the correct folder
  860. $entryName = substr($relativePath, 0, $pos);
  861. foreach ($files as &$entry) {
  862. if ($entry['name'] === $entryName) {
  863. $entry['size'] += $rootEntry['size'];
  864. }
  865. }
  866. } else { //mountpoint in this folder, add an entry for it
  867. $rootEntry['name'] = $relativePath;
  868. $rootEntry['type'] = $rootEntry['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file';
  869. $permissions = $rootEntry['permissions'];
  870. // do not allow renaming/deleting the mount point if they are not shared files/folders
  871. // for shared files/folders we use the permissions given by the owner
  872. if ($subStorage instanceof \OC\Files\Storage\Shared) {
  873. $rootEntry['permissions'] = $permissions;
  874. } else {
  875. $rootEntry['permissions'] = $permissions & (\OCP\PERMISSION_ALL - (\OCP\PERMISSION_UPDATE | \OCP\PERMISSION_DELETE));
  876. }
  877. //remove any existing entry with the same name
  878. foreach ($files as $i => $file) {
  879. if ($file['name'] === $rootEntry['name']) {
  880. unset($files[$i]);
  881. break;
  882. }
  883. }
  884. $rootEntry['path'] = substr($path . '/' . $rootEntry['name'], strlen($user) + 2); // full path without /$user/
  885. $files[] = new FileInfo($path . '/' . $rootEntry['name'], $subStorage, '', $rootEntry);
  886. }
  887. }
  888. }
  889. }
  890. if ($mimetype_filter) {
  891. foreach ($files as $file) {
  892. if (strpos($mimetype_filter, '/')) {
  893. if ($file['mimetype'] === $mimetype_filter) {
  894. $result[] = $file;
  895. }
  896. } else {
  897. if ($file['mimepart'] === $mimetype_filter) {
  898. $result[] = $file;
  899. }
  900. }
  901. }
  902. } else {
  903. $result = $files;
  904. }
  905. }
  906. return $result;
  907. }
  908. /**
  909. * change file metadata
  910. *
  911. * @param string $path
  912. * @param array|\OCP\Files\FileInfo $data
  913. * @return int
  914. *
  915. * returns the fileid of the updated file
  916. */
  917. public function putFileInfo($path, $data) {
  918. $this->assertPathLength($path);
  919. if ($data instanceof FileInfo) {
  920. $data = $data->getData();
  921. }
  922. $path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
  923. /**
  924. * @var \OC\Files\Storage\Storage $storage
  925. * @var string $internalPath
  926. */
  927. list($storage, $internalPath) = Filesystem::resolvePath($path);
  928. if ($storage) {
  929. $cache = $storage->getCache($path);
  930. if (!$cache->inCache($internalPath)) {
  931. $scanner = $storage->getScanner($internalPath);
  932. $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
  933. }
  934. return $cache->put($internalPath, $data);
  935. } else {
  936. return -1;
  937. }
  938. }
  939. /**
  940. * search for files with the name matching $query
  941. *
  942. * @param string $query
  943. * @return FileInfo[]
  944. */
  945. public function search($query) {
  946. return $this->searchCommon('%' . $query . '%', 'search');
  947. }
  948. /**
  949. * search for files by mimetype
  950. *
  951. * @param string $mimetype
  952. * @return FileInfo[]
  953. */
  954. public function searchByMime($mimetype) {
  955. return $this->searchCommon($mimetype, 'searchByMime');
  956. }
  957. /**
  958. * @param string $query
  959. * @param string $method
  960. * @return FileInfo[]
  961. */
  962. private function searchCommon($query, $method) {
  963. $files = array();
  964. $rootLength = strlen($this->fakeRoot);
  965. $mountPoint = Filesystem::getMountPoint($this->fakeRoot);
  966. $storage = Filesystem::getStorage($mountPoint);
  967. if ($storage) {
  968. $cache = $storage->getCache('');
  969. $results = $cache->$method($query);
  970. foreach ($results as $result) {
  971. if (substr($mountPoint . $result['path'], 0, $rootLength + 1) === $this->fakeRoot . '/') {
  972. $internalPath = $result['path'];
  973. $result['path'] = substr($mountPoint . $result['path'], $rootLength);
  974. $files[] = new FileInfo($mountPoint . $result['path'], $storage, $internalPath, $result);
  975. }
  976. }
  977. $mountPoints = Filesystem::getMountPoints($this->fakeRoot);
  978. foreach ($mountPoints as $mountPoint) {
  979. $storage = Filesystem::getStorage($mountPoint);
  980. if ($storage) {
  981. $cache = $storage->getCache('');
  982. $relativeMountPoint = substr($mountPoint, $rootLength);
  983. $results = $cache->$method($query);
  984. if ($results) {
  985. foreach ($results as $result) {
  986. $internalPath = $result['path'];
  987. $result['path'] = rtrim($relativeMountPoint . $result['path'], '/');
  988. $path = rtrim($mountPoint . $internalPath, '/');
  989. $files[] = new FileInfo($path, $storage, $internalPath, $result);
  990. }
  991. }
  992. }
  993. }
  994. }
  995. return $files;
  996. }
  997. /**
  998. * Get the owner for a file or folder
  999. *
  1000. * @param string $path
  1001. * @return string
  1002. */
  1003. public function getOwner($path) {
  1004. return $this->basicOperation('getOwner', $path);
  1005. }
  1006. /**
  1007. * get the ETag for a file or folder
  1008. *
  1009. * @param string $path
  1010. * @return string
  1011. */
  1012. public function getETag($path) {
  1013. /**
  1014. * @var Storage\Storage $storage
  1015. * @var string $internalPath
  1016. */
  1017. list($storage, $internalPath) = $this->resolvePath($path);
  1018. if ($storage) {
  1019. return $storage->getETag($internalPath);
  1020. } else {
  1021. return null;
  1022. }
  1023. }
  1024. /**
  1025. * Get the path of a file by id, relative to the view
  1026. *
  1027. * Note that the resulting path is not guarantied to be unique for the id, multiple paths can point to the same file
  1028. *
  1029. * @param int $id
  1030. * @return string
  1031. */
  1032. public function getPath($id) {
  1033. $manager = Filesystem::getMountManager();
  1034. $mounts = $manager->findIn($this->fakeRoot);
  1035. $mounts[] = $manager->find($this->fakeRoot);
  1036. // reverse the array so we start with the storage this view is in
  1037. // which is the most likely to contain the file we're looking for
  1038. $mounts = array_reverse($mounts);
  1039. foreach ($mounts as $mount) {
  1040. /**
  1041. * @var \OC\Files\Mount\Mount $mount
  1042. */
  1043. $cache = $mount->getStorage()->getCache();
  1044. $internalPath = $cache->getPathById($id);
  1045. if (is_string($internalPath)) {
  1046. $fullPath = $mount->getMountPoint() . $internalPath;
  1047. if (!is_null($path = $this->getRelativePath($fullPath))) {
  1048. return $path;
  1049. }
  1050. }
  1051. }
  1052. return null;
  1053. }
  1054. private function assertPathLength($path) {
  1055. $maxLen = min(PHP_MAXPATHLEN, 4000);
  1056. $pathLen = strlen($path);
  1057. if ($pathLen > $maxLen) {
  1058. throw new \OCP\Files\InvalidPathException("Path length($pathLen) exceeds max path length($maxLen): $path");
  1059. }
  1060. }
  1061. }