You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Encoding.php 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016, ownCloud, Inc.
  4. *
  5. * @author Lukas Reschke <lukas@statuscode.ch>
  6. * @author Robin Appelman <robin@icewind.nl>
  7. * @author Roeland Jago Douma <roeland@famdouma.nl>
  8. * @author Vincent Petry <pvince81@owncloud.com>
  9. *
  10. * @license AGPL-3.0
  11. *
  12. * This code is free software: you can redistribute it and/or modify
  13. * it under the terms of the GNU Affero General Public License, version 3,
  14. * as published by the Free Software Foundation.
  15. *
  16. * This program is distributed in the hope that it will be useful,
  17. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. * GNU Affero General Public License for more details.
  20. *
  21. * You should have received a copy of the GNU Affero General Public License, version 3,
  22. * along with this program. If not, see <http://www.gnu.org/licenses/>
  23. *
  24. */
  25. namespace OC\Files\Storage\Wrapper;
  26. use OC\Cache\CappedMemoryCache;
  27. use OCP\Files\Storage\IStorage;
  28. use OCP\ICache;
  29. /**
  30. * Encoding wrapper that deals with file names that use unsupported encodings like NFD.
  31. *
  32. * When applied and a UTF-8 path name was given, the wrapper will first attempt to access
  33. * the actual given name and then try its NFD form.
  34. */
  35. class Encoding extends Wrapper {
  36. /**
  37. * @var ICache
  38. */
  39. private $namesCache;
  40. /**
  41. * @param array $parameters
  42. */
  43. public function __construct($parameters) {
  44. $this->storage = $parameters['storage'];
  45. $this->namesCache = new CappedMemoryCache();
  46. }
  47. /**
  48. * Returns whether the given string is only made of ASCII characters
  49. *
  50. * @param string $str string
  51. *
  52. * @return bool true if the string is all ASCII, false otherwise
  53. */
  54. private function isAscii($str) {
  55. return (bool) !preg_match('/[\\x80-\\xff]+/', $str);
  56. }
  57. /**
  58. * Checks whether the given path exists in NFC or NFD form after checking
  59. * each form for each path section and returns the correct form.
  60. * If no existing path found, returns the path as it was given.
  61. *
  62. * @param string $fullPath path to check
  63. *
  64. * @return string original or converted path
  65. */
  66. private function findPathToUse($fullPath) {
  67. $cachedPath = $this->namesCache[$fullPath];
  68. if ($cachedPath !== null) {
  69. return $cachedPath;
  70. }
  71. $sections = explode('/', $fullPath);
  72. $path = '';
  73. foreach ($sections as $section) {
  74. $convertedPath = $this->findPathToUseLastSection($path, $section);
  75. if ($convertedPath === null) {
  76. // no point in continuing if the section was not found, use original path
  77. return $fullPath;
  78. }
  79. $path = $convertedPath . '/';
  80. }
  81. $path = rtrim($path, '/');
  82. return $path;
  83. }
  84. /**
  85. * Checks whether the last path section of the given path exists in NFC or NFD form
  86. * and returns the correct form. If no existing path found, returns null.
  87. *
  88. * @param string $basePath base path to check
  89. * @param string $lastSection last section of the path to check for NFD/NFC variations
  90. *
  91. * @return string|null original or converted path, or null if none of the forms was found
  92. */
  93. private function findPathToUseLastSection($basePath, $lastSection) {
  94. $fullPath = $basePath . $lastSection;
  95. if ($lastSection === '' || $this->isAscii($lastSection) || $this->storage->file_exists($fullPath)) {
  96. $this->namesCache[$fullPath] = $fullPath;
  97. return $fullPath;
  98. }
  99. // swap encoding
  100. if (\Normalizer::isNormalized($lastSection, \Normalizer::FORM_C)) {
  101. $otherFormPath = \Normalizer::normalize($lastSection, \Normalizer::FORM_D);
  102. } else {
  103. $otherFormPath = \Normalizer::normalize($lastSection, \Normalizer::FORM_C);
  104. }
  105. $otherFullPath = $basePath . $otherFormPath;
  106. if ($this->storage->file_exists($otherFullPath)) {
  107. $this->namesCache[$fullPath] = $otherFullPath;
  108. return $otherFullPath;
  109. }
  110. // return original path, file did not exist at all
  111. $this->namesCache[$fullPath] = $fullPath;
  112. return null;
  113. }
  114. /**
  115. * see https://www.php.net/manual/en/function.mkdir.php
  116. *
  117. * @param string $path
  118. * @return bool
  119. */
  120. public function mkdir($path) {
  121. // note: no conversion here, method should not be called with non-NFC names!
  122. $result = $this->storage->mkdir($path);
  123. if ($result) {
  124. $this->namesCache[$path] = $path;
  125. }
  126. return $result;
  127. }
  128. /**
  129. * see https://www.php.net/manual/en/function.rmdir.php
  130. *
  131. * @param string $path
  132. * @return bool
  133. */
  134. public function rmdir($path) {
  135. $result = $this->storage->rmdir($this->findPathToUse($path));
  136. if ($result) {
  137. unset($this->namesCache[$path]);
  138. }
  139. return $result;
  140. }
  141. /**
  142. * see https://www.php.net/manual/en/function.opendir.php
  143. *
  144. * @param string $path
  145. * @return resource
  146. */
  147. public function opendir($path) {
  148. return $this->storage->opendir($this->findPathToUse($path));
  149. }
  150. /**
  151. * see https://www.php.net/manual/en/function.is_dir.php
  152. *
  153. * @param string $path
  154. * @return bool
  155. */
  156. public function is_dir($path) {
  157. return $this->storage->is_dir($this->findPathToUse($path));
  158. }
  159. /**
  160. * see https://www.php.net/manual/en/function.is_file.php
  161. *
  162. * @param string $path
  163. * @return bool
  164. */
  165. public function is_file($path) {
  166. return $this->storage->is_file($this->findPathToUse($path));
  167. }
  168. /**
  169. * see https://www.php.net/manual/en/function.stat.php
  170. * only the following keys are required in the result: size and mtime
  171. *
  172. * @param string $path
  173. * @return array
  174. */
  175. public function stat($path) {
  176. return $this->storage->stat($this->findPathToUse($path));
  177. }
  178. /**
  179. * see https://www.php.net/manual/en/function.filetype.php
  180. *
  181. * @param string $path
  182. * @return bool
  183. */
  184. public function filetype($path) {
  185. return $this->storage->filetype($this->findPathToUse($path));
  186. }
  187. /**
  188. * see https://www.php.net/manual/en/function.filesize.php
  189. * The result for filesize when called on a folder is required to be 0
  190. *
  191. * @param string $path
  192. * @return int
  193. */
  194. public function filesize($path) {
  195. return $this->storage->filesize($this->findPathToUse($path));
  196. }
  197. /**
  198. * check if a file can be created in $path
  199. *
  200. * @param string $path
  201. * @return bool
  202. */
  203. public function isCreatable($path) {
  204. return $this->storage->isCreatable($this->findPathToUse($path));
  205. }
  206. /**
  207. * check if a file can be read
  208. *
  209. * @param string $path
  210. * @return bool
  211. */
  212. public function isReadable($path) {
  213. return $this->storage->isReadable($this->findPathToUse($path));
  214. }
  215. /**
  216. * check if a file can be written to
  217. *
  218. * @param string $path
  219. * @return bool
  220. */
  221. public function isUpdatable($path) {
  222. return $this->storage->isUpdatable($this->findPathToUse($path));
  223. }
  224. /**
  225. * check if a file can be deleted
  226. *
  227. * @param string $path
  228. * @return bool
  229. */
  230. public function isDeletable($path) {
  231. return $this->storage->isDeletable($this->findPathToUse($path));
  232. }
  233. /**
  234. * check if a file can be shared
  235. *
  236. * @param string $path
  237. * @return bool
  238. */
  239. public function isSharable($path) {
  240. return $this->storage->isSharable($this->findPathToUse($path));
  241. }
  242. /**
  243. * get the full permissions of a path.
  244. * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php
  245. *
  246. * @param string $path
  247. * @return int
  248. */
  249. public function getPermissions($path) {
  250. return $this->storage->getPermissions($this->findPathToUse($path));
  251. }
  252. /**
  253. * see https://www.php.net/manual/en/function.file_exists.php
  254. *
  255. * @param string $path
  256. * @return bool
  257. */
  258. public function file_exists($path) {
  259. return $this->storage->file_exists($this->findPathToUse($path));
  260. }
  261. /**
  262. * see https://www.php.net/manual/en/function.filemtime.php
  263. *
  264. * @param string $path
  265. * @return int
  266. */
  267. public function filemtime($path) {
  268. return $this->storage->filemtime($this->findPathToUse($path));
  269. }
  270. /**
  271. * see https://www.php.net/manual/en/function.file_get_contents.php
  272. *
  273. * @param string $path
  274. * @return string
  275. */
  276. public function file_get_contents($path) {
  277. return $this->storage->file_get_contents($this->findPathToUse($path));
  278. }
  279. /**
  280. * see https://www.php.net/manual/en/function.file_put_contents.php
  281. *
  282. * @param string $path
  283. * @param string $data
  284. * @return bool
  285. */
  286. public function file_put_contents($path, $data) {
  287. return $this->storage->file_put_contents($this->findPathToUse($path), $data);
  288. }
  289. /**
  290. * see https://www.php.net/manual/en/function.unlink.php
  291. *
  292. * @param string $path
  293. * @return bool
  294. */
  295. public function unlink($path) {
  296. $result = $this->storage->unlink($this->findPathToUse($path));
  297. if ($result) {
  298. unset($this->namesCache[$path]);
  299. }
  300. return $result;
  301. }
  302. /**
  303. * see https://www.php.net/manual/en/function.rename.php
  304. *
  305. * @param string $path1
  306. * @param string $path2
  307. * @return bool
  308. */
  309. public function rename($path1, $path2) {
  310. // second name always NFC
  311. return $this->storage->rename($this->findPathToUse($path1), $this->findPathToUse($path2));
  312. }
  313. /**
  314. * see https://www.php.net/manual/en/function.copy.php
  315. *
  316. * @param string $path1
  317. * @param string $path2
  318. * @return bool
  319. */
  320. public function copy($path1, $path2) {
  321. return $this->storage->copy($this->findPathToUse($path1), $this->findPathToUse($path2));
  322. }
  323. /**
  324. * see https://www.php.net/manual/en/function.fopen.php
  325. *
  326. * @param string $path
  327. * @param string $mode
  328. * @return resource
  329. */
  330. public function fopen($path, $mode) {
  331. $result = $this->storage->fopen($this->findPathToUse($path), $mode);
  332. if ($result && $mode !== 'r' && $mode !== 'rb') {
  333. unset($this->namesCache[$path]);
  334. }
  335. return $result;
  336. }
  337. /**
  338. * get the mimetype for a file or folder
  339. * The mimetype for a folder is required to be "httpd/unix-directory"
  340. *
  341. * @param string $path
  342. * @return string
  343. */
  344. public function getMimeType($path) {
  345. return $this->storage->getMimeType($this->findPathToUse($path));
  346. }
  347. /**
  348. * see https://www.php.net/manual/en/function.hash.php
  349. *
  350. * @param string $type
  351. * @param string $path
  352. * @param bool $raw
  353. * @return string
  354. */
  355. public function hash($type, $path, $raw = false) {
  356. return $this->storage->hash($type, $this->findPathToUse($path), $raw);
  357. }
  358. /**
  359. * see https://www.php.net/manual/en/function.free_space.php
  360. *
  361. * @param string $path
  362. * @return int
  363. */
  364. public function free_space($path) {
  365. return $this->storage->free_space($this->findPathToUse($path));
  366. }
  367. /**
  368. * search for occurrences of $query in file names
  369. *
  370. * @param string $query
  371. * @return array
  372. */
  373. public function search($query) {
  374. return $this->storage->search($query);
  375. }
  376. /**
  377. * see https://www.php.net/manual/en/function.touch.php
  378. * If the backend does not support the operation, false should be returned
  379. *
  380. * @param string $path
  381. * @param int $mtime
  382. * @return bool
  383. */
  384. public function touch($path, $mtime = null) {
  385. return $this->storage->touch($this->findPathToUse($path), $mtime);
  386. }
  387. /**
  388. * get the path to a local version of the file.
  389. * The local version of the file can be temporary and doesn't have to be persistent across requests
  390. *
  391. * @param string $path
  392. * @return string
  393. */
  394. public function getLocalFile($path) {
  395. return $this->storage->getLocalFile($this->findPathToUse($path));
  396. }
  397. /**
  398. * check if a file or folder has been updated since $time
  399. *
  400. * @param string $path
  401. * @param int $time
  402. * @return bool
  403. *
  404. * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed.
  405. * returning true for other changes in the folder is optional
  406. */
  407. public function hasUpdated($path, $time) {
  408. return $this->storage->hasUpdated($this->findPathToUse($path), $time);
  409. }
  410. /**
  411. * get a cache instance for the storage
  412. *
  413. * @param string $path
  414. * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache
  415. * @return \OC\Files\Cache\Cache
  416. */
  417. public function getCache($path = '', $storage = null) {
  418. if (!$storage) {
  419. $storage = $this;
  420. }
  421. return $this->storage->getCache($this->findPathToUse($path), $storage);
  422. }
  423. /**
  424. * get a scanner instance for the storage
  425. *
  426. * @param string $path
  427. * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner
  428. * @return \OC\Files\Cache\Scanner
  429. */
  430. public function getScanner($path = '', $storage = null) {
  431. if (!$storage) {
  432. $storage = $this;
  433. }
  434. return $this->storage->getScanner($this->findPathToUse($path), $storage);
  435. }
  436. /**
  437. * get the ETag for a file or folder
  438. *
  439. * @param string $path
  440. * @return string
  441. */
  442. public function getETag($path) {
  443. return $this->storage->getETag($this->findPathToUse($path));
  444. }
  445. /**
  446. * @param IStorage $sourceStorage
  447. * @param string $sourceInternalPath
  448. * @param string $targetInternalPath
  449. * @return bool
  450. */
  451. public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
  452. if ($sourceStorage === $this) {
  453. return $this->copy($sourceInternalPath, $this->findPathToUse($targetInternalPath));
  454. }
  455. $result = $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $this->findPathToUse($targetInternalPath));
  456. if ($result) {
  457. unset($this->namesCache[$targetInternalPath]);
  458. }
  459. return $result;
  460. }
  461. /**
  462. * @param IStorage $sourceStorage
  463. * @param string $sourceInternalPath
  464. * @param string $targetInternalPath
  465. * @return bool
  466. */
  467. public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
  468. if ($sourceStorage === $this) {
  469. $result = $this->rename($sourceInternalPath, $this->findPathToUse($targetInternalPath));
  470. if ($result) {
  471. unset($this->namesCache[$sourceInternalPath]);
  472. unset($this->namesCache[$targetInternalPath]);
  473. }
  474. return $result;
  475. }
  476. $result = $this->storage->moveFromStorage($sourceStorage, $sourceInternalPath, $this->findPathToUse($targetInternalPath));
  477. if ($result) {
  478. unset($this->namesCache[$sourceInternalPath]);
  479. unset($this->namesCache[$targetInternalPath]);
  480. }
  481. return $result;
  482. }
  483. /**
  484. * @param string $path
  485. * @return array
  486. */
  487. public function getMetaData($path) {
  488. return $this->storage->getMetaData($this->findPathToUse($path));
  489. }
  490. public function getDirectoryContent($directory): \Traversable {
  491. return $this->storage->getDirectoryContent($this->findPathToUse($directory));
  492. }
  493. }