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.

384 lines
9.0 KiB

3 years ago
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016, ownCloud, Inc.
  4. *
  5. * @author Bart Visscher <bartv@thisnet.nl>
  6. * @author Christopher Schäpers <kondou@ts.unde.re>
  7. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  8. * @author Joas Schilling <coding@schilljs.com>
  9. * @author Jörn Friedrich Dreyer <jfd@butonic.de>
  10. * @author Morris Jobke <hey@morrisjobke.de>
  11. * @author Remco Brenninkmeijer <requist1@starmail.nl>
  12. * @author Robin Appelman <robin@icewind.nl>
  13. * @author Robin McCorkell <robin@mccorkell.me.uk>
  14. * @author Roeland Jago Douma <roeland@famdouma.nl>
  15. * @author Thomas Müller <thomas.mueller@tmit.eu>
  16. *
  17. * @license AGPL-3.0
  18. *
  19. * This code is free software: you can redistribute it and/or modify
  20. * it under the terms of the GNU Affero General Public License, version 3,
  21. * as published by the Free Software Foundation.
  22. *
  23. * This program is distributed in the hope that it will be useful,
  24. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  25. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  26. * GNU Affero General Public License for more details.
  27. *
  28. * You should have received a copy of the GNU Affero General Public License, version 3,
  29. * along with this program. If not, see <http://www.gnu.org/licenses/>
  30. *
  31. */
  32. namespace OC\Archive;
  33. use Icewind\Streams\CallbackWrapper;
  34. class TAR extends Archive {
  35. public const PLAIN = 0;
  36. public const GZIP = 1;
  37. public const BZIP = 2;
  38. private $fileList;
  39. private $cachedHeaders;
  40. /**
  41. * @var \Archive_Tar tar
  42. */
  43. private $tar = null;
  44. private $path;
  45. /**
  46. * @param string $source
  47. */
  48. public function __construct($source) {
  49. $types = [null, 'gz', 'bz2'];
  50. $this->path = $source;
  51. $this->tar = new \Archive_Tar($source, $types[self::getTarType($source)]);
  52. }
  53. /**
  54. * try to detect the type of tar compression
  55. *
  56. * @param string $file
  57. * @return integer
  58. */
  59. public static function getTarType($file) {
  60. if (strpos($file, '.')) {
  61. $extension = substr($file, strrpos($file, '.'));
  62. switch ($extension) {
  63. case '.gz':
  64. case '.tgz':
  65. return self::GZIP;
  66. case '.bz':
  67. case '.bz2':
  68. return self::BZIP;
  69. case '.tar':
  70. return self::PLAIN;
  71. default:
  72. return self::PLAIN;
  73. }
  74. } else {
  75. return self::PLAIN;
  76. }
  77. }
  78. /**
  79. * add an empty folder to the archive
  80. *
  81. * @param string $path
  82. * @return bool
  83. */
  84. public function addFolder($path) {
  85. $tmpBase = \OC::$server->getTempManager()->getTemporaryFolder();
  86. $path = rtrim($path, '/') . '/';
  87. if ($this->fileExists($path)) {
  88. return false;
  89. }
  90. $parts = explode('/', $path);
  91. $folder = $tmpBase;
  92. foreach ($parts as $part) {
  93. $folder .= '/' . $part;
  94. if (!is_dir($folder)) {
  95. mkdir($folder);
  96. }
  97. }
  98. $result = $this->tar->addModify([$tmpBase . $path], '', $tmpBase);
  99. rmdir($tmpBase . $path);
  100. $this->fileList = false;
  101. $this->cachedHeaders = false;
  102. return $result;
  103. }
  104. /**
  105. * add a file to the archive
  106. *
  107. * @param string $path
  108. * @param string $source either a local file or string data
  109. * @return bool
  110. */
  111. public function addFile($path, $source = '') {
  112. if ($this->fileExists($path)) {
  113. $this->remove($path);
  114. }
  115. if ($source and $source[0] == '/' and file_exists($source)) {
  116. $source = file_get_contents($source);
  117. }
  118. $result = $this->tar->addString($path, $source);
  119. $this->fileList = false;
  120. $this->cachedHeaders = false;
  121. return $result;
  122. }
  123. /**
  124. * rename a file or folder in the archive
  125. *
  126. * @param string $source
  127. * @param string $dest
  128. * @return bool
  129. */
  130. public function rename($source, $dest) {
  131. //no proper way to delete, rename entire archive, rename file and remake archive
  132. $tmp = \OC::$server->getTempManager()->getTemporaryFolder();
  133. $this->tar->extract($tmp);
  134. rename($tmp . $source, $tmp . $dest);
  135. $this->tar = null;
  136. unlink($this->path);
  137. $types = [null, 'gz', 'bz'];
  138. $this->tar = new \Archive_Tar($this->path, $types[self::getTarType($this->path)]);
  139. $this->tar->createModify([$tmp], '', $tmp . '/');
  140. $this->fileList = false;
  141. $this->cachedHeaders = false;
  142. return true;
  143. }
  144. /**
  145. * @param string $file
  146. */
  147. private function getHeader($file) {
  148. if (!$this->cachedHeaders) {
  149. $this->cachedHeaders = $this->tar->listContent();
  150. }
  151. foreach ($this->cachedHeaders as $header) {
  152. if ($file == $header['filename']
  153. or $file . '/' == $header['filename']
  154. or '/' . $file . '/' == $header['filename']
  155. or '/' . $file == $header['filename']
  156. ) {
  157. return $header;
  158. }
  159. }
  160. return null;
  161. }
  162. /**
  163. * get the uncompressed size of a file in the archive
  164. *
  165. * @param string $path
  166. * @return int
  167. */
  168. public function filesize($path) {
  169. $stat = $this->getHeader($path);
  170. return $stat['size'];
  171. }
  172. /**
  173. * get the last modified time of a file in the archive
  174. *
  175. * @param string $path
  176. * @return int
  177. */
  178. public function mtime($path) {
  179. $stat = $this->getHeader($path);
  180. return $stat['mtime'];
  181. }
  182. /**
  183. * get the files in a folder
  184. *
  185. * @param string $path
  186. * @return array
  187. */
  188. public function getFolder($path) {
  189. $files = $this->getFiles();
  190. $folderContent = [];
  191. $pathLength = strlen($path);
  192. foreach ($files as $file) {
  193. if ($file[0] == '/') {
  194. $file = substr($file, 1);
  195. }
  196. if (substr($file, 0, $pathLength) == $path and $file != $path) {
  197. $result = substr($file, $pathLength);
  198. if ($pos = strpos($result, '/')) {
  199. $result = substr($result, 0, $pos + 1);
  200. }
  201. if (array_search($result, $folderContent) === false) {
  202. $folderContent[] = $result;
  203. }
  204. }
  205. }
  206. return $folderContent;
  207. }
  208. /**
  209. * get all files in the archive
  210. *
  211. * @return array
  212. */
  213. public function getFiles() {
  214. if ($this->fileList) {
  215. return $this->fileList;
  216. }
  217. if (!$this->cachedHeaders) {
  218. $this->cachedHeaders = $this->tar->listContent();
  219. }
  220. $files = [];
  221. foreach ($this->cachedHeaders as $header) {
  222. $files[] = $header['filename'];
  223. }
  224. $this->fileList = $files;
  225. return $files;
  226. }
  227. /**
  228. * get the content of a file
  229. *
  230. * @param string $path
  231. * @return string
  232. */
  233. public function getFile($path) {
  234. return $this->tar->extractInString($path);
  235. }
  236. /**
  237. * extract a single file from the archive
  238. *
  239. * @param string $path
  240. * @param string $dest
  241. * @return bool
  242. */
  243. public function extractFile($path, $dest) {
  244. $tmp = \OC::$server->getTempManager()->getTemporaryFolder();
  245. if (!$this->fileExists($path)) {
  246. return false;
  247. }
  248. if ($this->fileExists('/' . $path)) {
  249. $success = $this->tar->extractList(['/' . $path], $tmp);
  250. } else {
  251. $success = $this->tar->extractList([$path], $tmp);
  252. }
  253. if ($success) {
  254. rename($tmp . $path, $dest);
  255. }
  256. \OCP\Files::rmdirr($tmp);
  257. return $success;
  258. }
  259. /**
  260. * extract the archive
  261. *
  262. * @param string $dest
  263. * @return bool
  264. */
  265. public function extract($dest) {
  266. return $this->tar->extract($dest);
  267. }
  268. /**
  269. * check if a file or folder exists in the archive
  270. *
  271. * @param string $path
  272. * @return bool
  273. */
  274. public function fileExists($path) {
  275. $files = $this->getFiles();
  276. if ((array_search($path, $files) !== false) or (array_search($path . '/', $files) !== false)) {
  277. return true;
  278. } else {
  279. $folderPath = rtrim($path, '/') . '/';
  280. $pathLength = strlen($folderPath);
  281. foreach ($files as $file) {
  282. if (strlen($file) > $pathLength and substr($file, 0, $pathLength) == $folderPath) {
  283. return true;
  284. }
  285. }
  286. }
  287. if ($path[0] != '/') { //not all programs agree on the use of a leading /
  288. return $this->fileExists('/' . $path);
  289. } else {
  290. return false;
  291. }
  292. }
  293. /**
  294. * remove a file or folder from the archive
  295. *
  296. * @param string $path
  297. * @return bool
  298. */
  299. public function remove($path) {
  300. if (!$this->fileExists($path)) {
  301. return false;
  302. }
  303. $this->fileList = false;
  304. $this->cachedHeaders = false;
  305. //no proper way to delete, extract entire archive, delete file and remake archive
  306. $tmp = \OC::$server->getTempManager()->getTemporaryFolder();
  307. $this->tar->extract($tmp);
  308. \OCP\Files::rmdirr($tmp . $path);
  309. $this->tar = null;
  310. unlink($this->path);
  311. $this->reopen();
  312. $this->tar->createModify([$tmp], '', $tmp);
  313. return true;
  314. }
  315. /**
  316. * get a file handler
  317. *
  318. * @param string $path
  319. * @param string $mode
  320. * @return resource
  321. */
  322. public function getStream($path, $mode) {
  323. if (strrpos($path, '.') !== false) {
  324. $ext = substr($path, strrpos($path, '.'));
  325. } else {
  326. $ext = '';
  327. }
  328. $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
  329. if ($this->fileExists($path)) {
  330. $this->extractFile($path, $tmpFile);
  331. } elseif ($mode == 'r' or $mode == 'rb') {
  332. return false;
  333. }
  334. if ($mode == 'r' or $mode == 'rb') {
  335. return fopen($tmpFile, $mode);
  336. } else {
  337. $handle = fopen($tmpFile, $mode);
  338. return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
  339. $this->writeBack($tmpFile, $path);
  340. });
  341. }
  342. }
  343. /**
  344. * write back temporary files
  345. */
  346. public function writeBack($tmpFile, $path) {
  347. $this->addFile($path, $tmpFile);
  348. unlink($tmpFile);
  349. }
  350. /**
  351. * reopen the archive to ensure everything is written
  352. */
  353. private function reopen() {
  354. if ($this->tar) {
  355. $this->tar->_close();
  356. $this->tar = null;
  357. }
  358. $types = [null, 'gz', 'bz'];
  359. $this->tar = new \Archive_Tar($this->path, $types[self::getTarType($this->path)]);
  360. }
  361. }