PageRenderTime 44ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/filestorage/zip_packer.php

https://bitbucket.org/kudutest1/moodlegit
PHP | 499 lines | 338 code | 52 blank | 109 comment | 73 complexity | eca3f2a0421a0fa0b875a6f8d5cb3b62 MD5 | raw file
  1. <?php
  2. // This file is part of Moodle - http://moodle.org/
  3. //
  4. // Moodle is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // Moodle is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
  16. /**
  17. * Implementation of zip packer.
  18. *
  19. * @package core_files
  20. * @copyright 2008 Petr Skoda (http://skodak.org)
  21. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  22. */
  23. defined('MOODLE_INTERNAL') || die();
  24. require_once("$CFG->libdir/filestorage/file_packer.php");
  25. require_once("$CFG->libdir/filestorage/zip_archive.php");
  26. /**
  27. * Utility class - handles all zipping and unzipping operations.
  28. *
  29. * @package core_files
  30. * @category files
  31. * @copyright 2008 Petr Skoda (http://skodak.org)
  32. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  33. */
  34. class zip_packer extends file_packer {
  35. /**
  36. * Zip files and store the result in file storage.
  37. *
  38. * @param array $files array with full zip paths (including directory information)
  39. * as keys (archivepath=>ospathname or archivepath/subdir=>stored_file or archivepath=>array('content_as_string'))
  40. * @param int $contextid context ID
  41. * @param string $component component
  42. * @param string $filearea file area
  43. * @param int $itemid item ID
  44. * @param string $filepath file path
  45. * @param string $filename file name
  46. * @param int $userid user ID
  47. * @param bool $ignoreinvalidfiles true means ignore missing or invalid files, false means abort on any error
  48. * @return stored_file|bool false if error stored_file instance if ok
  49. */
  50. public function archive_to_storage(array $files, $contextid, $component, $filearea, $itemid, $filepath, $filename, $userid = NULL, $ignoreinvalidfiles=true) {
  51. global $CFG;
  52. $fs = get_file_storage();
  53. check_dir_exists($CFG->tempdir.'/zip');
  54. $tmpfile = tempnam($CFG->tempdir.'/zip', 'zipstor');
  55. if ($result = $this->archive_to_pathname($files, $tmpfile, $ignoreinvalidfiles)) {
  56. if ($file = $fs->get_file($contextid, $component, $filearea, $itemid, $filepath, $filename)) {
  57. if (!$file->delete()) {
  58. @unlink($tmpfile);
  59. return false;
  60. }
  61. }
  62. $file_record = new stdClass();
  63. $file_record->contextid = $contextid;
  64. $file_record->component = $component;
  65. $file_record->filearea = $filearea;
  66. $file_record->itemid = $itemid;
  67. $file_record->filepath = $filepath;
  68. $file_record->filename = $filename;
  69. $file_record->userid = $userid;
  70. $file_record->mimetype = 'application/zip';
  71. $result = $fs->create_file_from_pathname($file_record, $tmpfile);
  72. }
  73. @unlink($tmpfile);
  74. return $result;
  75. }
  76. /**
  77. * Zip files and store the result in os file.
  78. *
  79. * @param array $files array with zip paths as keys (archivepath=>ospathname or archivepath=>stored_file or archivepath=>array('content_as_string'))
  80. * @param string $archivefile path to target zip file
  81. * @param bool $ignoreinvalidfiles true means ignore missing or invalid files, false means abort on any error
  82. * @return bool true if file created, false if not
  83. */
  84. public function archive_to_pathname(array $files, $archivefile, $ignoreinvalidfiles=true) {
  85. $ziparch = new zip_archive();
  86. if (!$ziparch->open($archivefile, file_archive::OVERWRITE)) {
  87. return false;
  88. }
  89. $abort = false;
  90. foreach ($files as $archivepath => $file) {
  91. $archivepath = trim($archivepath, '/');
  92. if (is_null($file)) {
  93. // Directories have null as content.
  94. if (!$ziparch->add_directory($archivepath.'/')) {
  95. debugging("Can not zip '$archivepath' directory", DEBUG_DEVELOPER);
  96. if (!$ignoreinvalidfiles) {
  97. $abort = true;
  98. break;
  99. }
  100. }
  101. } else if (is_string($file)) {
  102. if (!$this->archive_pathname($ziparch, $archivepath, $file)) {
  103. debugging("Can not zip '$archivepath' file", DEBUG_DEVELOPER);
  104. if (!$ignoreinvalidfiles) {
  105. $abort = true;
  106. break;
  107. }
  108. }
  109. } else if (is_array($file)) {
  110. $content = reset($file);
  111. if (!$ziparch->add_file_from_string($archivepath, $content)) {
  112. debugging("Can not zip '$archivepath' file", DEBUG_DEVELOPER);
  113. if (!$ignoreinvalidfiles) {
  114. $abort = true;
  115. break;
  116. }
  117. }
  118. } else {
  119. if (!$this->archive_stored($ziparch, $archivepath, $file)) {
  120. debugging("Can not zip '$archivepath' file", DEBUG_DEVELOPER);
  121. if (!$ignoreinvalidfiles) {
  122. $abort = true;
  123. break;
  124. }
  125. }
  126. }
  127. }
  128. if (!$ziparch->close()) {
  129. @unlink($archivefile);
  130. return false;
  131. }
  132. if ($abort) {
  133. @unlink($archivefile);
  134. return false;
  135. }
  136. return true;
  137. }
  138. /**
  139. * Perform archiving file from stored file.
  140. *
  141. * @param zip_archive $ziparch zip archive instance
  142. * @param string $archivepath file path to archive
  143. * @param stored_file $file stored_file object
  144. * @return bool success
  145. */
  146. private function archive_stored($ziparch, $archivepath, $file) {
  147. $result = $file->archive_file($ziparch, $archivepath);
  148. if (!$result) {
  149. return false;
  150. }
  151. if (!$file->is_directory()) {
  152. return true;
  153. }
  154. $baselength = strlen($file->get_filepath());
  155. $fs = get_file_storage();
  156. $files = $fs->get_directory_files($file->get_contextid(), $file->get_component(), $file->get_filearea(), $file->get_itemid(),
  157. $file->get_filepath(), true, true);
  158. foreach ($files as $file) {
  159. $path = $file->get_filepath();
  160. $path = substr($path, $baselength);
  161. $path = $archivepath.'/'.$path;
  162. if (!$file->is_directory()) {
  163. $path = $path.$file->get_filename();
  164. }
  165. // Ignore result here, partial zipping is ok for now.
  166. $file->archive_file($ziparch, $path);
  167. }
  168. return true;
  169. }
  170. /**
  171. * Perform archiving file from file path.
  172. *
  173. * @param zip_archive $ziparch zip archive instance
  174. * @param string $archivepath file path to archive
  175. * @param string $file path name of the file
  176. * @return bool success
  177. */
  178. private function archive_pathname($ziparch, $archivepath, $file) {
  179. if (!file_exists($file)) {
  180. return false;
  181. }
  182. if (is_file($file)) {
  183. if (!is_readable($file)) {
  184. return false;
  185. }
  186. return $ziparch->add_file_from_pathname($archivepath, $file);
  187. }
  188. if (is_dir($file)) {
  189. if ($archivepath !== '') {
  190. $ziparch->add_directory($archivepath);
  191. }
  192. $files = new DirectoryIterator($file);
  193. foreach ($files as $file) {
  194. if ($file->isDot()) {
  195. continue;
  196. }
  197. $newpath = $archivepath.'/'.$file->getFilename();
  198. $this->archive_pathname($ziparch, $newpath, $file->getPathname());
  199. }
  200. unset($files); // Release file handles.
  201. return true;
  202. }
  203. }
  204. /**
  205. * Unzip file to given file path (real OS filesystem), existing files are overwritten.
  206. *
  207. * @todo MDL-31048 localise messages
  208. * @param string|stored_file $archivefile full pathname of zip file or stored_file instance
  209. * @param string $pathname target directory
  210. * @param array $onlyfiles only extract files present in the array. The path to files MUST NOT
  211. * start with a /. Example: array('myfile.txt', 'directory/anotherfile.txt')
  212. * @return bool|array list of processed files; false if error
  213. */
  214. public function extract_to_pathname($archivefile, $pathname, array $onlyfiles = null) {
  215. global $CFG;
  216. if (!is_string($archivefile)) {
  217. return $archivefile->extract_to_pathname($this, $pathname);
  218. }
  219. $processed = array();
  220. $pathname = rtrim($pathname, '/');
  221. if (!is_readable($archivefile)) {
  222. return false;
  223. }
  224. $ziparch = new zip_archive();
  225. if (!$ziparch->open($archivefile, file_archive::OPEN)) {
  226. return false;
  227. }
  228. foreach ($ziparch as $info) {
  229. $size = $info->size;
  230. $name = $info->pathname;
  231. if ($name === '' or array_key_exists($name, $processed)) {
  232. // Probably filename collisions caused by filename cleaning/conversion.
  233. continue;
  234. } else if (is_array($onlyfiles) && !in_array($name, $onlyfiles)) {
  235. // Skipping files which are not in the list.
  236. continue;
  237. }
  238. if ($info->is_directory) {
  239. $newdir = "$pathname/$name";
  240. // directory
  241. if (is_file($newdir) and !unlink($newdir)) {
  242. $processed[$name] = 'Can not create directory, file already exists'; // TODO: localise
  243. continue;
  244. }
  245. if (is_dir($newdir)) {
  246. //dir already there
  247. $processed[$name] = true;
  248. } else {
  249. if (mkdir($newdir, $CFG->directorypermissions, true)) {
  250. $processed[$name] = true;
  251. } else {
  252. $processed[$name] = 'Can not create directory'; // TODO: localise
  253. }
  254. }
  255. continue;
  256. }
  257. $parts = explode('/', trim($name, '/'));
  258. $filename = array_pop($parts);
  259. $newdir = rtrim($pathname.'/'.implode('/', $parts), '/');
  260. if (!is_dir($newdir)) {
  261. if (!mkdir($newdir, $CFG->directorypermissions, true)) {
  262. $processed[$name] = 'Can not create directory'; // TODO: localise
  263. continue;
  264. }
  265. }
  266. $newfile = "$newdir/$filename";
  267. if (!$fp = fopen($newfile, 'wb')) {
  268. $processed[$name] = 'Can not write target file'; // TODO: localise
  269. continue;
  270. }
  271. if (!$fz = $ziparch->get_stream($info->index)) {
  272. $processed[$name] = 'Can not read file from zip archive'; // TODO: localise
  273. fclose($fp);
  274. continue;
  275. }
  276. while (!feof($fz)) {
  277. $content = fread($fz, 262143);
  278. fwrite($fp, $content);
  279. }
  280. fclose($fz);
  281. fclose($fp);
  282. if (filesize($newfile) !== $size) {
  283. $processed[$name] = 'Unknown error during zip extraction'; // TODO: localise
  284. // something went wrong :-(
  285. @unlink($newfile);
  286. continue;
  287. }
  288. $processed[$name] = true;
  289. }
  290. $ziparch->close();
  291. return $processed;
  292. }
  293. /**
  294. * Unzip file to given file path (real OS filesystem), existing files are overwritten.
  295. *
  296. * @todo MDL-31048 localise messages
  297. * @param string|stored_file $archivefile full pathname of zip file or stored_file instance
  298. * @param int $contextid context ID
  299. * @param string $component component
  300. * @param string $filearea file area
  301. * @param int $itemid item ID
  302. * @param string $pathbase file path
  303. * @param int $userid user ID
  304. * @return array|bool list of processed files; false if error
  305. */
  306. public function extract_to_storage($archivefile, $contextid, $component, $filearea, $itemid, $pathbase, $userid = NULL) {
  307. global $CFG;
  308. if (!is_string($archivefile)) {
  309. return $archivefile->extract_to_storage($this, $contextid, $component, $filearea, $itemid, $pathbase, $userid);
  310. }
  311. check_dir_exists($CFG->tempdir.'/zip');
  312. $pathbase = trim($pathbase, '/');
  313. $pathbase = ($pathbase === '') ? '/' : '/'.$pathbase.'/';
  314. $fs = get_file_storage();
  315. $processed = array();
  316. $ziparch = new zip_archive();
  317. if (!$ziparch->open($archivefile, file_archive::OPEN)) {
  318. return false;
  319. }
  320. foreach ($ziparch as $info) {
  321. $size = $info->size;
  322. $name = $info->pathname;
  323. if ($name === '' or array_key_exists($name, $processed)) {
  324. //probably filename collisions caused by filename cleaning/conversion
  325. continue;
  326. }
  327. if ($info->is_directory) {
  328. $newfilepath = $pathbase.$name.'/';
  329. $fs->create_directory($contextid, $component, $filearea, $itemid, $newfilepath, $userid);
  330. $processed[$name] = true;
  331. continue;
  332. }
  333. $parts = explode('/', trim($name, '/'));
  334. $filename = array_pop($parts);
  335. $filepath = $pathbase;
  336. if ($parts) {
  337. $filepath .= implode('/', $parts).'/';
  338. }
  339. if ($size < 2097151) {
  340. // Small file.
  341. if (!$fz = $ziparch->get_stream($info->index)) {
  342. $processed[$name] = 'Can not read file from zip archive'; // TODO: localise
  343. continue;
  344. }
  345. $content = '';
  346. while (!feof($fz)) {
  347. $content .= fread($fz, 262143);
  348. }
  349. fclose($fz);
  350. if (strlen($content) !== $size) {
  351. $processed[$name] = 'Unknown error during zip extraction'; // TODO: localise
  352. // something went wrong :-(
  353. unset($content);
  354. continue;
  355. }
  356. if ($file = $fs->get_file($contextid, $component, $filearea, $itemid, $filepath, $filename)) {
  357. if (!$file->delete()) {
  358. $processed[$name] = 'Can not delete existing file'; // TODO: localise
  359. continue;
  360. }
  361. }
  362. $file_record = new stdClass();
  363. $file_record->contextid = $contextid;
  364. $file_record->component = $component;
  365. $file_record->filearea = $filearea;
  366. $file_record->itemid = $itemid;
  367. $file_record->filepath = $filepath;
  368. $file_record->filename = $filename;
  369. $file_record->userid = $userid;
  370. if ($fs->create_file_from_string($file_record, $content)) {
  371. $processed[$name] = true;
  372. } else {
  373. $processed[$name] = 'Unknown error during zip extraction'; // TODO: localise
  374. }
  375. unset($content);
  376. continue;
  377. } else {
  378. // large file, would not fit into memory :-(
  379. $tmpfile = tempnam($CFG->tempdir.'/zip', 'unzip');
  380. if (!$fp = fopen($tmpfile, 'wb')) {
  381. @unlink($tmpfile);
  382. $processed[$name] = 'Can not write temp file'; // TODO: localise
  383. continue;
  384. }
  385. if (!$fz = $ziparch->get_stream($info->index)) {
  386. @unlink($tmpfile);
  387. $processed[$name] = 'Can not read file from zip archive'; // TODO: localise
  388. continue;
  389. }
  390. while (!feof($fz)) {
  391. $content = fread($fz, 262143);
  392. fwrite($fp, $content);
  393. }
  394. fclose($fz);
  395. fclose($fp);
  396. if (filesize($tmpfile) !== $size) {
  397. $processed[$name] = 'Unknown error during zip extraction'; // TODO: localise
  398. // something went wrong :-(
  399. @unlink($tmpfile);
  400. continue;
  401. }
  402. if ($file = $fs->get_file($contextid, $component, $filearea, $itemid, $filepath, $filename)) {
  403. if (!$file->delete()) {
  404. @unlink($tmpfile);
  405. $processed[$name] = 'Can not delete existing file'; // TODO: localise
  406. continue;
  407. }
  408. }
  409. $file_record = new stdClass();
  410. $file_record->contextid = $contextid;
  411. $file_record->component = $component;
  412. $file_record->filearea = $filearea;
  413. $file_record->itemid = $itemid;
  414. $file_record->filepath = $filepath;
  415. $file_record->filename = $filename;
  416. $file_record->userid = $userid;
  417. if ($fs->create_file_from_pathname($file_record, $tmpfile)) {
  418. $processed[$name] = true;
  419. } else {
  420. $processed[$name] = 'Unknown error during zip extraction'; // TODO: localise
  421. }
  422. @unlink($tmpfile);
  423. continue;
  424. }
  425. }
  426. $ziparch->close();
  427. return $processed;
  428. }
  429. /**
  430. * Returns array of info about all files in archive.
  431. *
  432. * @param string|file_archive $archivefile
  433. * @return array of file infos
  434. */
  435. public function list_files($archivefile) {
  436. if (!is_string($archivefile)) {
  437. return $archivefile->list_files();
  438. }
  439. $ziparch = new zip_archive();
  440. if (!$ziparch->open($archivefile, file_archive::OPEN)) {
  441. return false;
  442. }
  443. $list = $ziparch->list_files();
  444. $ziparch->close();
  445. return $list;
  446. }
  447. }