diff --git a/libraries/classes/OpenDocument.php b/libraries/classes/OpenDocument.php index 825b48fb9c..d03f765f46 100644 --- a/libraries/classes/OpenDocument.php +++ b/libraries/classes/OpenDocument.php @@ -7,7 +7,7 @@ */ namespace PhpMyAdmin; -use PhpMyAdmin\ZipFile; +use PhpMyAdmin\ZipExtension; /** * Simplfied OpenDocument creator class @@ -38,10 +38,9 @@ EOT; */ public static function create($mime, $data) { - $zipfile = new ZipFile(); - $zipfile -> addFile($mime, 'mimetype'); - $zipfile -> addFile($data, 'content.xml'); - $zipfile -> addFile( + $data = array( + $mime, + $data, '' . '' . '' . '', - 'meta.xml' - ); - $zipfile -> addFile( '' . '' @@ -153,9 +149,6 @@ EOT; . '' . '' . '', - 'styles.xml' - ); - $zipfile -> addFile( '' . '' @@ -167,9 +160,17 @@ EOT; . ' manifest:full-path="meta.xml"/>' . '' - . '', + . '' + ); + + $name = array( + 'mimetype', + 'content.xml', + 'meta.xml', + 'styles.xml', 'META-INF/manifest.xml' ); - return $zipfile -> file(); + + return ZipExtension::createFile($data, $name); } } diff --git a/libraries/classes/ZipExtension.php b/libraries/classes/ZipExtension.php index 6dd715c5d5..0f2ca23c64 100644 --- a/libraries/classes/ZipExtension.php +++ b/libraries/classes/ZipExtension.php @@ -19,7 +19,7 @@ class ZipExtension /** * Gets zip file contents * - * @param string $file zip file + * @param string $file path to zip file * @param string $specific_entry regular expression to match a file * * @return array ($error_message, $file_data); $error_message @@ -27,138 +27,117 @@ class ZipExtension */ public static function getContents($file, $specific_entry = null) { + /** + * This function is used to "import" a SQL file which has been exported earlier + * That means that this function works on the assumption that the zip file contains only a single SQL file + * It might also be an ODS file, look below + */ + $error_message = ''; $file_data = ''; - $zip_handle = zip_open($file); - if (!is_resource($zip_handle)) { - $error_message = __('Error in ZIP archive:') - . ' ' . self::getError($zip_handle); - return (array('error' => $error_message, 'data' => $file_data)); - } - $first_zip_entry = zip_read($zip_handle); - if (false === $first_zip_entry) { - $error_message = __('No files found inside ZIP archive!'); - zip_close($zip_handle); - return (array('error' => $error_message, 'data' => $file_data)); - } + $zip = new ZipArchive; + $res = $zip->open($file); - /* Is the the zip really an ODS file? */ - $read = zip_entry_read($first_zip_entry); - $ods_mime = 'application/vnd.oasis.opendocument.spreadsheet'; - if (!strcmp($ods_mime, $read)) { - $specific_entry = '/^content\.xml$/'; - } + if ($res === TRUE) { + if ($zip->numFiles === 0) { + $error_message = __('No files found inside ZIP archive!'); + $zip->close(); + return (array('error' => $error_message, 'data' => $file_data)); + } - if (!isset($specific_entry)) { - zip_entry_open($zip_handle, $first_zip_entry, 'r'); - /* File pointer has already been moved, - * so include what was read above */ - $file_data = $read; - $file_data .= zip_entry_read( - $first_zip_entry, - zip_entry_filesize($first_zip_entry) - ); - zip_entry_close($first_zip_entry); - zip_close($zip_handle); - return (array('error' => $error_message, 'data' => $file_data)); - } + /* Is the the zip really an ODS file? */ + $ods_mime = 'application/vnd.oasis.opendocument.spreadsheet'; + $first_zip_entry = $zip->getFromIndex(0); + if (!strcmp($ods_mime, $first_zip_entry)) { + $specific_entry = '/^content\.xml$/'; + } - /* Return the correct contents, not just the first entry */ - for ( ; ; ) { - $entry = zip_read($zip_handle); - if (is_resource($entry)) { - if (preg_match($specific_entry, zip_entry_name($entry))) { - zip_entry_open($zip_handle, $entry, 'r'); - $file_data = zip_entry_read( - $entry, - zip_entry_filesize($entry) - ); - zip_entry_close($entry); + if (!isset($specific_entry)) { + $file_data = $first_zip_entry; + $zip->close(); + return (array('error' => $error_message, 'data' => $file_data)); + } + + /* Return the correct contents, not just the first entry */ + for ($i = 0; $i < $zip->numFiles; $i++) { + if (@preg_match($specific_entry, $zip->getNameIndex($i))) { + $file_data = $zip->getFromIndex($i); break; } - } else { - /** - * Either we have reached the end of the zip and still - * haven't found $specific_entry or there was a parsing - * error that we must display - */ - if ($entry === false) { - $error_message = __('Error in ZIP archive:') - . ' Could not find "' . $specific_entry . '"'; - } else { - $error_message = __('Error in ZIP archive:') - . ' ' . self::getError($zip_handle); - } - - break; } - } - zip_close($zip_handle); - return (array('error' => $error_message, 'data' => $file_data)); + /* Couldn't find any files that matched $specific_entry */ + if (empty($file_data)) { + $error_message = __('Error in ZIP archive:') + . ' Could not find "' . $specific_entry . '"'; + } + + $zip->close(); + return (array('error' => $error_message, 'data' => $file_data)); + } else { + $error_message = __('Error in ZIP archive:') . ' ' . $zip->getStatusString(); + $zip->close(); + return (array('error' => $error_message, 'data' => $file_data)); + } } /** - * Returns the file name of the first file that matches the given $file_regexp. + * Returns the filename of the first file that matches the given $file_regexp. * - * @param string $file_regexp regular expression for the file name to match - * @param string $file zip archive + * @param string $file path to zip file + * @param string $regex regular expression for the file name to match * - * @return string the file name of the first file that matches the given regexp + * @return string the file name of the first file that matches the given regular expression */ - public static function findFile($file_regexp, $file) + public static function findFile($file, $regex) { - $zip_handle = zip_open($file); - if (is_resource($zip_handle)) { - $entry = zip_read($zip_handle); - while (is_resource($entry)) { - if (preg_match($file_regexp, zip_entry_name($entry))) { - $file_name = zip_entry_name($entry); - zip_close($zip_handle); - return $file_name; + $zip = new ZipArchive; + $res = $zip->open($file); + + if ($res === TRUE) { + for ($i = 0; $i < $zip->numFiles; $i++) { + if (preg_match($regex, $zip->getNameIndex($i))) { + $filename = $zip->getNameIndex($i); + $zip->close(); + return $filename; } - $entry = zip_read($zip_handle); } } - zip_close($zip_handle); return false; } /** * Returns the number of files in the zip archive. * - * @param string $file zip archive + * @param string $file path to zip file * - * @return int the number of files in the zip archive + * @return int the number of files in the zip archive or 0, either if there wern't any files or an error occured. */ public static function getNumberOfFiles($file) { - $count = 0; - $zip_handle = zip_open($file); - if (is_resource($zip_handle)) { - $entry = zip_read($zip_handle); - while (is_resource($entry)) { - $count++; - $entry = zip_read($zip_handle); - } - zip_close($zip_handle); + $num = 0; + $zip = new ZipArchive; + $res = $zip->open($file); + + if ($res === TRUE) { + $num = $zip->numFiles; } - return $count; + return $num; } /** - * Extracts a set of files from the given zip archive to a given destinations. + * Extracts the content of $entry. * - * @param string $zip_path path to the zip archive - * @param string $entry file in the archive that should be extracted + * @param string $file path to zip file + * @param string $entry file in the archive that should be extracted * * @return string|bool data on sucess, false otherwise */ - public static function extract($zip_path, $entry) + public static function extract($file, $entry) { $zip = new ZipArchive; - if ($zip->open($zip_path) === true) { + if ($zip->open($file) === true) { $result = $zip->getFromName($entry); $zip->close(); return $result; @@ -167,34 +146,128 @@ class ZipExtension } /** - * Gets zip error message - * - * @param resource $code error code - * - * @return string error message + * Creates a zip file. + * If $data is an array and $name is a string, the filenames will be indexed. + * The function will return false if $data is a string but $name is an array or if $data is an array and $name is an array, but they don't have the same amount of elements. + * + * @param array|string $data contents of the file/files + * @param array|string $name name of the file/files in the archive + * @param integer $time the current timestamp + * + * @return string|bool the ZIP file contents, or false if there was an error. */ - public static function getError($code) + public static function createFile($data, $name, $time = 0) { - // I don't think this needs translation - switch ($code) { - case ZipArchive::ER_MULTIDISK: - $message = 'Multi-disk zip archives not supported'; - break; - case ZipArchive::ER_READ: - $message = 'Read error'; - break; - case ZipArchive::ER_CRC: - $message = 'CRC error'; - break; - case ZipArchive::ER_NOZIP: - $message = 'Not a zip archive'; - break; - case ZipArchive::ER_INCONS: - $message = 'Zip archive inconsistent'; - break; - default: - $message = $code; + $datasec = array(); // Array to store compressed data + $ctrl_dir = array(); // Central directory + $old_offset = 0; // Last offset position + $eof_ctrl_dir = "\x50\x4b\x05\x06\x00\x00\x00\x00"; // End of central directory record + $index = false; // Whether or not to index files + + if (count($name) === 1) { + $index = true; + $name = str_replace('\\', '/', $name); + $extension_pos = strpos($name, '.'); + $extension = substr($name, $extension_pos); + $name = substr($name, 0, -$extension_pos - 1); + } else { + if (count($name) != count($data)) { + return false; + } } - return $message; + + for ($i = 0; $i < count($data); $i++) { + if ($index) { + $temp_name = $name . '_' . $i . $extension; + } else { + $temp_name = str_replace('\\', '/', $name[$i]); + } + + /* Convert Unix timestamp to DOS timestamp */ + $timearray = ($time == 0) ? getdate() : getdate($time); + + if ($timearray['year'] < 1980) { + $timearray['year'] = 1980; + $timearray['mon'] = 1; + $timearray['mday'] = 1; + $timearray['hours'] = 0; + $timearray['minutes'] = 0; + $timearray['seconds'] = 0; + } + + $time = (($timearray['year'] - 1980) << 25) + | ($timearray['mon'] << 21) + | ($timearray['mday'] << 16) + | ($timearray['hours'] << 11) + | ($timearray['minutes'] << 5) + | ($timearray['seconds'] >> 1); + + $hexdtime = pack('V', $time); + + $fr = "\x50\x4b\x03\x04"; + $fr .= "\x14\x00"; // ver needed to extract + $fr .= "\x00\x00"; // gen purpose bit flag + $fr .= "\x08\x00"; // compression method + $fr .= $hexdtime; // last mod time and date + + // "local file header" segment + $unc_len = strlen($data[$i]); + $crc = crc32($data[$i]); + $zdata = gzcompress($data[$i]); + $zdata = substr(substr($zdata, 0, strlen($zdata) - 4), 2); // fix crc bug + $c_len = strlen($zdata); + $fr .= pack('V', $crc); // crc32 + $fr .= pack('V', $c_len); // compressed filesize + $fr .= pack('V', $unc_len); // uncompressed filesize + $fr .= pack('v', strlen($temp_name)); // length of filename + $fr .= pack('v', 0); // extra field length + $fr .= $temp_name; + + // "file data" segment + $fr .= $zdata; + + $datasec[] = $fr; + + // now add to central directory record + $cdrec = "\x50\x4b\x01\x02"; + $cdrec .= "\x00\x00"; // version made by + $cdrec .= "\x14\x00"; // version needed to extract + $cdrec .= "\x00\x00"; // gen purpose bit flag + $cdrec .= "\x08\x00"; // compression method + $cdrec .= $hexdtime; // last mod time & date + $cdrec .= pack('V', $crc); // crc32 + $cdrec .= pack('V', $c_len); // compressed filesize + $cdrec .= pack('V', $unc_len); // uncompressed filesize + $cdrec .= pack('v', strlen($temp_name)); // length of filename + $cdrec .= pack('v', 0); // extra field length + $cdrec .= pack('v', 0); // file comment length + $cdrec .= pack('v', 0); // disk number start + $cdrec .= pack('v', 0); // internal file attributes + $cdrec .= pack('V', 32); // external file attributes + // - 'archive' bit set + + $cdrec .= pack('V', $old_offset); // relative offset of local header + $old_offset += strlen($fr); + + $cdrec .= $temp_name; + + // optional extra field, file comment goes here + // save to central directory + $ctrl_dir[] = $cdrec; + } + + /* Build string to return */ + $temp_ctrldir = implode('', $ctrl_dir); + $header = $temp_ctrldir . + $eof_ctrl_dir . + pack('v', sizeof($ctrl_dir)) . //total #of entries "on this disk" + pack('v', sizeof($ctrl_dir)) . //total #of entries overall + pack('V', strlen($temp_ctrldir)) . //size of central dir + pack('V', $old_offset) . //offset to start of central dir + "\x00\x00"; //.zip file comment length + + $data = implode('', $datasec); + + return $data . $header; } } diff --git a/libraries/classes/ZipFile.php b/libraries/classes/ZipFile.php deleted file mode 100644 index 8ff13a277b..0000000000 --- a/libraries/classes/ZipFile.php +++ /dev/null @@ -1,202 +0,0 @@ - file - * - * @var boolean $doWrite - */ - var $doWrite = false; - /** - * Array to store compressed data - * - * @var array $datasec - */ - var $datasec = array(); - /** - * Central directory - * - * @var array $ctrl_dir - */ - var $ctrl_dir = array(); - /** - * End of central directory record - * - * @var string $eof_ctrl_dir - */ - var $eof_ctrl_dir = "\x50\x4b\x05\x06\x00\x00\x00\x00"; - /** - * Last offset position - * - * @var integer $old_offset - */ - var $old_offset = 0; - - /** - * Sets member variable this -> doWrite to true - * - Should be called immediately after class instantiation - * - If set to true, then ZIP archive are echo'ed to STDOUT as each - * file is added via this -> addfile(), and central directories are - * echoed to STDOUT on final call to this -> file(). Also, - * this -> file() returns an empty string so it is safe to issue a - * "echo $zipfile;" command - * - * @access public - * - * @return void - */ - function setDoWrite() - { - $this->doWrite = true; - } // end of the 'setDoWrite()' method - - /** - * Converts an Unix timestamp to a four byte DOS date and time format (date - * in high two bytes, time in low two bytes allowing magnitude comparison). - * - * @param integer $unixtime the current Unix timestamp - * - * @return integer the current date in a four byte DOS format - * - * @access private - */ - function unix2DosTime($unixtime = 0) - { - $timearray = ($unixtime == 0) ? getdate() : getdate($unixtime); - - if ($timearray['year'] < 1980) { - $timearray['year'] = 1980; - $timearray['mon'] = 1; - $timearray['mday'] = 1; - $timearray['hours'] = 0; - $timearray['minutes'] = 0; - $timearray['seconds'] = 0; - } // end if - - return (($timearray['year'] - 1980) << 25) - | ($timearray['mon'] << 21) - | ($timearray['mday'] << 16) - | ($timearray['hours'] << 11) - | ($timearray['minutes'] << 5) - | ($timearray['seconds'] >> 1); - } // end of the 'unix2DosTime()' method - - /** - * Adds "file" to archive - * - * @param string $data file contents - * @param string $name name of the file in the archive (may contains the path) - * @param integer $time the current timestamp - * - * @access public - * - * @return void - */ - function addFile($data, $name, $time = 0) - { - $name = str_replace('\\', '/', $name); - - $hexdtime = pack('V', $this->unix2DosTime($time)); - - $fr = "\x50\x4b\x03\x04"; - $fr .= "\x14\x00"; // ver needed to extract - $fr .= "\x00\x00"; // gen purpose bit flag - $fr .= "\x08\x00"; // compression method - $fr .= $hexdtime; // last mod time and date - - // "local file header" segment - $unc_len = strlen($data); - $crc = crc32($data); - $zdata = gzcompress($data); - $zdata = substr(substr($zdata, 0, strlen($zdata) - 4), 2); // fix crc bug - $c_len = strlen($zdata); - $fr .= pack('V', $crc); // crc32 - $fr .= pack('V', $c_len); // compressed filesize - $fr .= pack('V', $unc_len); // uncompressed filesize - $fr .= pack('v', strlen($name)); // length of filename - $fr .= pack('v', 0); // extra field length - $fr .= $name; - - // "file data" segment - $fr .= $zdata; - - // echo this entry on the fly, ... - if ($this->doWrite) { - echo $fr; - } else { // ... OR add this entry to array - $this->datasec[] = $fr; - } - - // now add to central directory record - $cdrec = "\x50\x4b\x01\x02"; - $cdrec .= "\x00\x00"; // version made by - $cdrec .= "\x14\x00"; // version needed to extract - $cdrec .= "\x00\x00"; // gen purpose bit flag - $cdrec .= "\x08\x00"; // compression method - $cdrec .= $hexdtime; // last mod time & date - $cdrec .= pack('V', $crc); // crc32 - $cdrec .= pack('V', $c_len); // compressed filesize - $cdrec .= pack('V', $unc_len); // uncompressed filesize - $cdrec .= pack('v', strlen($name)); // length of filename - $cdrec .= pack('v', 0); // extra field length - $cdrec .= pack('v', 0); // file comment length - $cdrec .= pack('v', 0); // disk number start - $cdrec .= pack('v', 0); // internal file attributes - $cdrec .= pack('V', 32); // external file attributes - // - 'archive' bit set - - $cdrec .= pack('V', $this->old_offset); // relative offset of local header - $this->old_offset += strlen($fr); - - $cdrec .= $name; - - // optional extra field, file comment goes here - // save to central directory - $this->ctrl_dir[] = $cdrec; - } // end of the 'addFile()' method - - /** - * Echo central dir if ->doWrite==true, else build string to return - * - * @return string if ->doWrite {empty string} else the ZIP file contents - * - * @access public - */ - function file() - { - $ctrldir = implode('', $this->ctrl_dir); - $header = $ctrldir . - $this->eof_ctrl_dir . - pack('v', sizeof($this->ctrl_dir)) . //total #of entries "on this disk" - pack('v', sizeof($this->ctrl_dir)) . //total #of entries overall - pack('V', strlen($ctrldir)) . //size of central dir - pack('V', $this->old_offset) . //offset to start of central dir - "\x00\x00"; //.zip file comment length - - if ($this->doWrite) { // Send central directory & end ctrl dir to STDOUT - echo $header; - - return ""; // Return empty string - } else { // Return entire ZIP archive as string - $data = implode('', $this->datasec); - - return $data . $header; - } - } // end of the 'file()' method -} // end of the 'PhpMyAdmin\ZipFile' class diff --git a/libraries/export.lib.php b/libraries/export.lib.php index 553c892541..7e85a9f5c6 100644 --- a/libraries/export.lib.php +++ b/libraries/export.lib.php @@ -10,7 +10,7 @@ use PhpMyAdmin\Encoding; use PhpMyAdmin\Message; use PMA\libraries\plugins\ExportPlugin; use PhpMyAdmin\Table; -use PhpMyAdmin\ZipFile; +use PhpMyAdmin\ZipExtension; use PhpMyAdmin\Url; use PhpMyAdmin\Sanitize; @@ -405,24 +405,7 @@ function PMA_compressExport($dump_buffer, $compression, $filename) { if ($compression == 'zip' && @function_exists('gzcompress')) { $filename = substr($filename, 0, -4); // remove extension (.zip) - $zipfile = new ZipFile(); - if (is_array($dump_buffer)) { - foreach ($dump_buffer as $table => $dump) { - $ext_pos = strpos($filename, '.'); - $extension = substr($filename, $ext_pos); - $zipfile->addFile( - $dump, - str_replace( - $extension, - '_' . $table . $extension, - $filename - ) - ); - } - } else { - $zipfile->addFile($dump_buffer, $filename); - } - $dump_buffer = $zipfile->file(); + $dump_buffer = ZipExtension::createFile($dump_buffer, $filename); } elseif ($compression == 'gzip' && PMA_gzencodeNeeded()) { // without the optional parameter level because it bugs $dump_buffer = gzencode($dump_buffer); diff --git a/libraries/plugins/import/ImportShp.php b/libraries/plugins/import/ImportShp.php index 1d3cfb8372..151c92b125 100644 --- a/libraries/plugins/import/ImportShp.php +++ b/libraries/plugins/import/ImportShp.php @@ -92,8 +92,8 @@ class ImportShp extends ImportPlugin // and use the files in it for import if ($compression == 'application/zip' && ! is_null($temp)) { $dbf_file_name = ZipExtension::findFile( - '/^.*\.dbf$/i', - $import_file + $import_file, + '/^.*\.dbf$/i' ); // If the corresponding .dbf file is in the zip archive if ($dbf_file_name) { diff --git a/test/classes/ZipExtensionTest.php b/test/classes/ZipExtensionTest.php index f72d151d06..d0f575e192 100644 --- a/test/classes/ZipExtensionTest.php +++ b/test/classes/ZipExtensionTest.php @@ -17,9 +17,9 @@ require_once 'test/PMATestCase.php'; class ZipExtensionTest extends PMATestCase { /** - * Test zip file content + * Test for ZipExtension::getContents * - * @param string $file zip file + * @param string $file path to zip file * @param string $specific_entry regular expression to match a file * @param mixed $output expected output * @@ -62,19 +62,19 @@ class ZipExtensionTest extends PMATestCase } /** - * Test Find file in Zip Archive + * Test for ZipExtension::findFile * + * @param string $file path to zip file * @param string $file_regexp regular expression for the file name to match - * @param string $file zip archive * @param mixed $output expected output * * @dataProvider provideTestFindFile * @return void */ - public function testFindFile($file_regexp, $file, $output) + public function testFindFile($file, $file_regexp, $output) { $this->assertEquals( - ZipExtension::findFile($file_regexp, $file), + ZipExtension::findFile($file, $file_regexp), $output ); } @@ -88,8 +88,8 @@ class ZipExtensionTest extends PMATestCase { return array( array( - '/test/', './test/test_data/test.zip', + '/test/', 'test.file' ) ); @@ -130,54 +130,20 @@ class ZipExtensionTest extends PMATestCase } /** - * Test for ZipExtension::getError + * Test for ZipExtension::createFile * - * @param int $code error code - * @param mixed $output expected output - * - * @dataProvider provideTestGetError * @return void */ - public function testGetError($code, $output) + public function testCreateFile() { - $this->assertEquals( - ZipExtension::getError($code), - $output - ); - } + $file = ZipExtension::createFile("Test content", "test.txt"); + $this->assertTrue(!empty($file)); - /** - * Provider for testGetZipError - * - * @return array - */ - public function provideTestGetError() - { - return array( - array( - 1, - 'Multi-disk zip archives not supported' - ), - array( - 5, - 'Read error' - ), - array( - 7, - 'CRC error' - ), - array( - 19, - 'Not a zip archive' - ), - array( - 21, - 'Zip archive inconsistent' - ), - array( - 404, - 404 - ) + $this->assertEquals( + false, + ZipExtension::createFile( + "Content", + array("name1.txt", "name2.txt")) ); } } diff --git a/test/classes/ZipFileTest.php b/test/classes/ZipFileTest.php deleted file mode 100644 index 5fcde00bce..0000000000 --- a/test/classes/ZipFileTest.php +++ /dev/null @@ -1,123 +0,0 @@ -object = new ZipFile(); - - } - - /** - * Tears down the fixture, for example, closes a network connection. - * This method is called after a test is executed. - * - * @access protected - * @return void - */ - protected function tearDown() - { - unset($this->object); - } - - /** - * Test for setDoWrite - * - * @return void - */ - public function testSetDoWrite() - { - $this->object->setDoWrite(); - $this->assertTrue($this->object->doWrite); - } - - /** - * Test for unix2DosTime - * - * @param int $unixTime UNIX timestamp - * @param int $output DOS timestamp - * - * @dataProvider providerForTestUnix2DosTime - * - * @return void - */ - public function testUnix2DosTime($unixTime, $output) - { - $this->assertEquals( - $this->object->unix2DosTime($unixTime), - $output - ); - } - - /** - * Provider for testUnix2DosTime - * - * @return array - */ - public function providerForTestUnix2DosTime() - { - return array( - array( - 123456, - 2162688 - ), - array( - 234232, - 2162688 - ), - ); - } - - /** - * Test for addFile - * - * @return void - */ - public function testAddFile() - { - $this->assertEquals( - $this->object->addFile('This is test content for the file', 'Test file'), - '' - ); - $this->assertTrue(!empty($this->object->ctrl_dir)); - } - - /** - * Test for file - * - * @return void - */ - public function testFile() - { - $file = $this->object->file(); - $this->assertTrue( - !empty($file) - ); - } -}