ZipExtension.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. <?php
  2. /* vim: set expandtab sw=4 ts=4 sts=4: */
  3. /**
  4. * Interface for the zip extension
  5. *
  6. * @package PhpMyAdmin
  7. */
  8. declare(strict_types=1);
  9. namespace PhpMyAdmin;
  10. use ZipArchive;
  11. /**
  12. * Transformations class
  13. *
  14. * @package PhpMyAdmin
  15. */
  16. class ZipExtension
  17. {
  18. /**
  19. * @var ZipArchive
  20. */
  21. private $zip;
  22. /**
  23. * Constructor
  24. */
  25. public function __construct()
  26. {
  27. $this->zip = new ZipArchive();
  28. }
  29. /**
  30. * Gets zip file contents
  31. *
  32. * @param string $file path to zip file
  33. * @param string $specific_entry regular expression to match a file
  34. *
  35. * @return array ($error_message, $file_data); $error_message
  36. * is empty if no error
  37. */
  38. public function getContents($file, $specific_entry = null)
  39. {
  40. /**
  41. * This function is used to "import" a SQL file which has been exported earlier
  42. * That means that this function works on the assumption that the zip file contains only a single SQL file
  43. * It might also be an ODS file, look below
  44. */
  45. $error_message = '';
  46. $file_data = '';
  47. $res = $this->zip->open($file);
  48. if ($res === true) {
  49. if ($this->zip->numFiles === 0) {
  50. $error_message = __('No files found inside ZIP archive!');
  51. $this->zip->close();
  52. return [
  53. 'error' => $error_message,
  54. 'data' => $file_data,
  55. ];
  56. }
  57. /* Is the the zip really an ODS file? */
  58. $ods_mime = 'application/vnd.oasis.opendocument.spreadsheet';
  59. $first_zip_entry = $this->zip->getFromIndex(0);
  60. if (! strcmp($ods_mime, $first_zip_entry)) {
  61. $specific_entry = '/^content\.xml$/';
  62. }
  63. if (! isset($specific_entry)) {
  64. $file_data = $first_zip_entry;
  65. $this->zip->close();
  66. return [
  67. 'error' => $error_message,
  68. 'data' => $file_data,
  69. ];
  70. }
  71. /* Return the correct contents, not just the first entry */
  72. for ($i = 0; $i < $this->zip->numFiles; $i++) {
  73. if (@preg_match($specific_entry, $this->zip->getNameIndex($i))) {
  74. $file_data = $this->zip->getFromIndex($i);
  75. break;
  76. }
  77. }
  78. /* Couldn't find any files that matched $specific_entry */
  79. if (empty($file_data)) {
  80. $error_message = __('Error in ZIP archive:')
  81. . ' Could not find "' . $specific_entry . '"';
  82. }
  83. $this->zip->close();
  84. return [
  85. 'error' => $error_message,
  86. 'data' => $file_data,
  87. ];
  88. } else {
  89. $error_message = __('Error in ZIP archive:') . ' ' . $this->zip->getStatusString();
  90. $this->zip->close();
  91. return [
  92. 'error' => $error_message,
  93. 'data' => $file_data,
  94. ];
  95. }
  96. }
  97. /**
  98. * Returns the filename of the first file that matches the given $file_regexp.
  99. *
  100. * @param string $file path to zip file
  101. * @param string $regex regular expression for the file name to match
  102. *
  103. * @return string|false the file name of the first file that matches the given regular expression
  104. */
  105. public function findFile($file, $regex)
  106. {
  107. $res = $this->zip->open($file);
  108. if ($res === true) {
  109. for ($i = 0; $i < $this->zip->numFiles; $i++) {
  110. if (preg_match($regex, $this->zip->getNameIndex($i))) {
  111. $filename = $this->zip->getNameIndex($i);
  112. $this->zip->close();
  113. return $filename;
  114. }
  115. }
  116. }
  117. return false;
  118. }
  119. /**
  120. * Returns the number of files in the zip archive.
  121. *
  122. * @param string $file path to zip file
  123. *
  124. * @return int the number of files in the zip archive or 0, either if there wern't any files or an error occured.
  125. */
  126. public function getNumberOfFiles($file)
  127. {
  128. $num = 0;
  129. $res = $this->zip->open($file);
  130. if ($res === true) {
  131. $num = $this->zip->numFiles;
  132. }
  133. return $num;
  134. }
  135. /**
  136. * Extracts the content of $entry.
  137. *
  138. * @param string $file path to zip file
  139. * @param string $entry file in the archive that should be extracted
  140. *
  141. * @return string|bool data on sucess, false otherwise
  142. */
  143. public function extract($file, $entry)
  144. {
  145. if ($this->zip->open($file) === true) {
  146. $result = $this->zip->getFromName($entry);
  147. $this->zip->close();
  148. return $result;
  149. }
  150. return false;
  151. }
  152. /**
  153. * Creates a zip file.
  154. * If $data is an array and $name is a string, the filenames will be indexed.
  155. * The function will return false if $data is a string but $name is an array
  156. * or if $data is an array and $name is an array, but they don't have the
  157. * same amount of elements.
  158. *
  159. * @param array|string $data contents of the file/files
  160. * @param array|string $name name of the file/files in the archive
  161. * @param integer $time the current timestamp
  162. *
  163. * @return string|bool the ZIP file contents, or false if there was an error.
  164. */
  165. public function createFile($data, $name, $time = 0)
  166. {
  167. $datasec = []; // Array to store compressed data
  168. $ctrl_dir = []; // Central directory
  169. $old_offset = 0; // Last offset position
  170. $eof_ctrl_dir = "\x50\x4b\x05\x06\x00\x00\x00\x00"; // End of central directory record
  171. if (is_string($data) && is_string($name)) {
  172. $data = [$name => $data];
  173. } elseif (is_array($data) && is_string($name)) {
  174. $ext_pos = strpos($name, '.');
  175. $extension = substr($name, $ext_pos);
  176. $newData = [];
  177. foreach ($data as $key => $value) {
  178. $newName = str_replace(
  179. $extension,
  180. '_' . $key . $extension,
  181. $name
  182. );
  183. $newData[$newName] = $value;
  184. }
  185. $data = $newData;
  186. } elseif (is_array($data) && is_array($name) && count($data) === count($name)) {
  187. $data = array_combine($name, $data);
  188. } else {
  189. return false;
  190. }
  191. foreach ($data as $table => $dump) {
  192. $temp_name = str_replace('\\', '/', $table);
  193. /* Get Local Time */
  194. $timearray = getdate();
  195. if ($timearray['year'] < 1980) {
  196. $timearray['year'] = 1980;
  197. $timearray['mon'] = 1;
  198. $timearray['mday'] = 1;
  199. $timearray['hours'] = 0;
  200. $timearray['minutes'] = 0;
  201. $timearray['seconds'] = 0;
  202. }
  203. $time = (($timearray['year'] - 1980) << 25)
  204. | ($timearray['mon'] << 21)
  205. | ($timearray['mday'] << 16)
  206. | ($timearray['hours'] << 11)
  207. | ($timearray['minutes'] << 5)
  208. | ($timearray['seconds'] >> 1);
  209. $hexdtime = pack('V', $time);
  210. $unc_len = strlen($dump);
  211. $crc = crc32($dump);
  212. $zdata = gzcompress($dump);
  213. $zdata = substr(substr($zdata, 0, strlen($zdata) - 4), 2); // fix crc bug
  214. $c_len = strlen($zdata);
  215. $fr = "\x50\x4b\x03\x04"
  216. . "\x14\x00" // ver needed to extract
  217. . "\x00\x00" // gen purpose bit flag
  218. . "\x08\x00" // compression method
  219. . $hexdtime // last mod time and date
  220. // "local file header" segment
  221. . pack('V', $crc) // crc32
  222. . pack('V', $c_len) // compressed filesize
  223. . pack('V', $unc_len) // uncompressed filesize
  224. . pack('v', strlen($temp_name)) // length of filename
  225. . pack('v', 0) // extra field length
  226. . $temp_name
  227. // "file data" segment
  228. . $zdata;
  229. $datasec[] = $fr;
  230. // now add to central directory record
  231. $cdrec = "\x50\x4b\x01\x02"
  232. . "\x00\x00" // version made by
  233. . "\x14\x00" // version needed to extract
  234. . "\x00\x00" // gen purpose bit flag
  235. . "\x08\x00" // compression method
  236. . $hexdtime // last mod time & date
  237. . pack('V', $crc) // crc32
  238. . pack('V', $c_len) // compressed filesize
  239. . pack('V', $unc_len) // uncompressed filesize
  240. . pack('v', strlen($temp_name)) // length of filename
  241. . pack('v', 0) // extra field length
  242. . pack('v', 0) // file comment length
  243. . pack('v', 0) // disk number start
  244. . pack('v', 0) // internal file attributes
  245. . pack('V', 32) // external file attributes
  246. // - 'archive' bit set
  247. . pack('V', $old_offset) // relative offset of local header
  248. . $temp_name; // filename
  249. $old_offset += strlen($fr);
  250. // optional extra field, file comment goes here
  251. // save to central directory
  252. $ctrl_dir[] = $cdrec;
  253. }
  254. /* Build string to return */
  255. $temp_ctrldir = implode('', $ctrl_dir);
  256. $header = $temp_ctrldir .
  257. $eof_ctrl_dir .
  258. pack('v', count($ctrl_dir)) . //total #of entries "on this disk"
  259. pack('v', count($ctrl_dir)) . //total #of entries overall
  260. pack('V', strlen($temp_ctrldir)) . //size of central dir
  261. pack('V', $old_offset) . //offset to start of central dir
  262. "\x00\x00"; //.zip file comment length
  263. $data = implode('', $datasec);
  264. return $data . $header;
  265. }
  266. }