Encoding.php 13 KB

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