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