ArchiveUnzip.class.php (17715B)
1 <?php 2 // 28/11/2005 (2.4) 3 // - dUnzip2 is now compliant with wrong placed "Data Description", made by some compressors, 4 // like the classes ZipLib and ZipLib2 by 'Hasin Hayder'. Thanks to Ricardo Parreno for pointing it. 5 // 09/11/2005 (2.3) 6 // - Added optional parameter '$stopOnFile' on method 'getList()'. 7 // If given, file listing will stop when find given filename. (Useful to open and unzip an exact file) 8 // 06/11/2005 (2.21) 9 // - Added support to PK00 file format (Packed to Removable Disk) (thanks to Lito [PHPfileNavigator]) 10 // - Method 'getExtraInfo': If requested file doesn't exist, return FALSE instead of Array() 11 // 31/10/2005 (2.2) 12 // - Removed redundant 'file_name' on centralDirs declaration (thanks to Lito [PHPfileNavigator]) 13 // - Fixed redeclaration of file_put_contents when in PHP4 (not returning true) 14 15 ############################################################## 16 # Class dUnzip2 v2.4 17 # 18 # Author: Alexandre Tedeschi (d) 19 # E-Mail: alexandrebr at gmail dot com 20 # Londrina - PR / Brazil 21 # 22 # Objective: 23 # This class allows programmer to easily unzip files on the fly. 24 # 25 # Requirements: 26 # This class requires extension ZLib Enabled. It is default 27 # for most site hosts around the world, and for the PHP Win32 dist. 28 # 29 # To do: 30 # * Error handling 31 # * Write a PHP-Side gzinflate, to completely avoid any external extensions 32 # * Write other decompress algorithms 33 # 34 # If you modify this class, or have any ideas to improve it, please contact me! 35 # You are allowed to redistribute this class, if you keep my name and contact e-mail on it. 36 ############################################################## 37 38 namespace util; 39 class ArchiveUnzip 40 { 41 42 // Public 43 var $files = array(); 44 var $value = ''; 45 var $fileName; 46 var $compressedList; // You will problably use only this one! 47 var $centralDirList; // Central dir list... It's a kind of 'extra attributes' for a set of files 48 var $endOfCentral; // End of central dir, contains ZIP Comments 49 var $debug; 50 51 // Private 52 var $fh; 53 var $zipSignature = "\x50\x4b\x03\x04"; // local file header signature 54 var $dirSignature = "\x50\x4b\x01\x02"; // central dir header signature 55 var $dirSignatureE = "\x50\x4b\x05\x06"; // end of central dir signature 56 57 // Public 58 Function __construct() 59 { 60 $this->compressedList = 61 $this->centralDirList = 62 $this->endOfCentral = Array(); 63 } 64 65 function open($value) 66 { 67 $this->fileName = tempnam('/tmp', 'unzip'); 68 // echo $this->fileName; 69 $fo = fopen($this->fileName, 'w'); 70 fwrite($fo, $value); 71 $this->unzipAll(); 72 } 73 74 75 Function getList($stopOnFile = false) 76 { 77 if (sizeof($this->compressedList)) { 78 $this->debugMsg(1, "Returning already loaded file list."); 79 return $this->compressedList; 80 } 81 82 // Open file, and set file handler 83 $fh = fopen($this->fileName, "r"); 84 $this->fh = &$fh; 85 if (!$fh) { 86 $this->debugMsg(2, "Failed to load file."); 87 return false; 88 } 89 90 // Loop the file, looking for files and folders 91 $ddTry = false; 92 fseek($fh, 0); 93 for (; ;) { 94 // Check if the signature is valid... 95 $signature = fread($fh, 4); 96 if (feof($fh)) { 97 # $this->debugMsg(1, "Reached end of file"); 98 break; 99 } 100 101 // If signature is a 'Packed to Removable Disk', just ignore it and move to the next. 102 if ($signature == 'PK00') { 103 $this->debugMsg(1, "Found PK00: Packed to Removable Disk"); 104 continue; 105 } 106 107 // If signature of a 'Local File Header' 108 if ($signature == $this->zipSignature) { 109 # $this->debugMsg(1, "Zip Signature!"); 110 111 // Get information about the zipped file 112 $file['version_needed'] = unpack("v", fread($fh, 2)); // version needed to extract 113 $file['general_bit_flag'] = unpack("v", fread($fh, 2)); // general purpose bit flag 114 $file['compression_method'] = unpack("v", fread($fh, 2)); // compression method 115 $file['lastmod_time'] = unpack("v", fread($fh, 2)); // last mod file time 116 $file['lastmod_date'] = unpack("v", fread($fh, 2)); // last mod file date 117 $file['crc-32'] = fread($fh, 4); // crc-32 118 $file['compressed_size'] = unpack("V", fread($fh, 4)); // compressed size 119 $file['uncompressed_size'] = unpack("V", fread($fh, 4)); // uncompressed size 120 $fileNameLength = unpack("v", fread($fh, 2)); // filename length 121 $extraFieldLength = unpack("v", fread($fh, 2)); // extra field length 122 $file['file_name'] = fread($fh, $fileNameLength[1]); // filename 123 $file['extra_field'] = $extraFieldLength[1] ? fread($fh, $extraFieldLength[1]) : ''; // extra field 124 $file['contents-startOffset'] = ftell($fh); 125 126 // Bypass the whole compressed contents, and look for the next file 127 fseek($fh, $file['compressed_size'][1], SEEK_CUR); 128 129 // Convert the date and time, from MS-DOS format to UNIX Timestamp 130 $BINlastmod_date = str_pad(decbin($file['lastmod_date'][1]), 16, '0', STR_PAD_LEFT); 131 $BINlastmod_time = str_pad(decbin($file['lastmod_time'][1]), 16, '0', STR_PAD_LEFT); 132 $lastmod_dateY = bindec(substr($BINlastmod_date, 0, 7)) + 1980; 133 $lastmod_dateM = bindec(substr($BINlastmod_date, 7, 4)); 134 $lastmod_dateD = bindec(substr($BINlastmod_date, 11, 5)); 135 $lastmod_timeH = bindec(substr($BINlastmod_time, 0, 5)); 136 $lastmod_timeM = bindec(substr($BINlastmod_time, 5, 6)); 137 $lastmod_timeS = bindec(substr($BINlastmod_time, 11, 5)); 138 139 // Mount file table 140 $this->compressedList[$file['file_name']] = Array( 141 'file_name' => $file['file_name'], 142 'compression_method' => $file['compression_method'][1], 143 'version_needed' => $file['version_needed'][1], 144 'lastmod_datetime' => mktime($lastmod_timeH, $lastmod_timeM, $lastmod_timeS, $lastmod_dateM, $lastmod_dateD, $lastmod_dateY), 145 'crc-32' => str_pad(dechex(ord($file['crc-32'][3])), 2, '0', STR_PAD_LEFT) . 146 str_pad(dechex(ord($file['crc-32'][2])), 2, '0', STR_PAD_LEFT) . 147 str_pad(dechex(ord($file['crc-32'][1])), 2, '0', STR_PAD_LEFT) . 148 str_pad(dechex(ord($file['crc-32'][0])), 2, '0', STR_PAD_LEFT), 149 'compressed_size' => $file['compressed_size'][1], 150 'uncompressed_size' => $file['uncompressed_size'][1], 151 'extra_field' => $file['extra_field'], 152 'general_bit_flag' => str_pad(decbin($file['general_bit_flag'][1]), 8, '0', STR_PAD_LEFT), 153 'contents-startOffset' => $file['contents-startOffset'] 154 ); 155 156 if ($stopOnFile) if ($file['file_name'] == $stopOnFile) { 157 $this->debugMsg(1, "Stopping on file..."); 158 break; 159 } 160 } // If signature of a 'Central Directory Structure' 161 elseif ($signature == $this->dirSignature) { 162 # $this->debugMsg(1, "Dir Signature!"); 163 164 $dir['version_madeby'] = unpack("v", fread($fh, 2)); // version made by 165 $dir['version_needed'] = unpack("v", fread($fh, 2)); // version needed to extract 166 $dir['general_bit_flag'] = unpack("v", fread($fh, 2)); // general purpose bit flag 167 $dir['compression_method'] = unpack("v", fread($fh, 2)); // compression method 168 $dir['lastmod_time'] = unpack("v", fread($fh, 2)); // last mod file time 169 $dir['lastmod_date'] = unpack("v", fread($fh, 2)); // last mod file date 170 $dir['crc-32'] = fread($fh, 4); // crc-32 171 $dir['compressed_size'] = unpack("V", fread($fh, 4)); // compressed size 172 $dir['uncompressed_size'] = unpack("V", fread($fh, 4)); // uncompressed size 173 $fileNameLength = unpack("v", fread($fh, 2)); // filename length 174 $extraFieldLength = unpack("v", fread($fh, 2)); // extra field length 175 $fileCommentLength = unpack("v", fread($fh, 2)); // file comment length 176 $dir['disk_number_start'] = unpack("v", fread($fh, 2)); // disk number start 177 $dir['internal_attributes'] = unpack("v", fread($fh, 2)); // internal file attributes-byte1 178 $dir['external_attributes1'] = unpack("v", fread($fh, 2)); // external file attributes-byte2 179 $dir['external_attributes2'] = unpack("v", fread($fh, 2)); // external file attributes 180 $dir['relative_offset'] = unpack("V", fread($fh, 4)); // relative offset of local header 181 $dir['file_name'] = fread($fh, $fileNameLength[1]); // filename 182 $dir['extra_field'] = $extraFieldLength[1] ? fread($fh, $extraFieldLength[1]) : ''; // extra field 183 $dir['file_comment'] = $fileCommentLength[1] ? fread($fh, $fileCommentLength[1]) : ''; // file comment 184 185 // Convert the date and time, from MS-DOS format to UNIX Timestamp 186 $BINlastmod_date = str_pad(decbin($file['lastmod_date'][1]), 16, '0', STR_PAD_LEFT); 187 $BINlastmod_time = str_pad(decbin($file['lastmod_time'][1]), 16, '0', STR_PAD_LEFT); 188 $lastmod_dateY = bindec(substr($BINlastmod_date, 0, 7)) + 1980; 189 $lastmod_dateM = bindec(substr($BINlastmod_date, 7, 4)); 190 $lastmod_dateD = bindec(substr($BINlastmod_date, 11, 5)); 191 $lastmod_timeH = bindec(substr($BINlastmod_time, 0, 5)); 192 $lastmod_timeM = bindec(substr($BINlastmod_time, 5, 6)); 193 $lastmod_timeS = bindec(substr($BINlastmod_time, 11, 5)); 194 195 $this->centralDirList[$dir['file_name']] = Array( 196 'version_madeby' => $dir['version_madeby'][1], 197 'version_needed' => $dir['version_needed'][1], 198 'general_bit_flag' => str_pad(decbin($file['general_bit_flag'][1]), 8, '0', STR_PAD_LEFT), 199 'compression_method' => $dir['compression_method'][1], 200 'lastmod_datetime' => mktime($lastmod_timeH, $lastmod_timeM, $lastmod_timeS, $lastmod_dateM, $lastmod_dateD, $lastmod_dateY), 201 'crc-32' => str_pad(dechex(ord($file['crc-32'][3])), 2, '0', STR_PAD_LEFT) . 202 str_pad(dechex(ord($file['crc-32'][2])), 2, '0', STR_PAD_LEFT) . 203 str_pad(dechex(ord($file['crc-32'][1])), 2, '0', STR_PAD_LEFT) . 204 str_pad(dechex(ord($file['crc-32'][0])), 2, '0', STR_PAD_LEFT), 205 'compressed_size' => $dir['compressed_size'][1], 206 'uncompressed_size' => $dir['uncompressed_size'][1], 207 'disk_number_start' => $dir['disk_number_start'][1], 208 'internal_attributes' => $dir['internal_attributes'][1], 209 'external_attributes1' => $dir['external_attributes1'][1], 210 'external_attributes2' => $dir['external_attributes2'][1], 211 'relative_offset' => $dir['relative_offset'][1], 212 'file_name' => $dir['file_name'], 213 'extra_field' => $dir['extra_field'], 214 'file_comment' => $dir['file_comment'], 215 ); 216 } elseif ($signature == $this->dirSignatureE) { 217 # $this->debugMsg(1, "EOF Dir Signature!"); 218 219 $eodir['disk_number_this'] = unpack("v", fread($fh, 2)); // number of this disk 220 $eodir['disk_number'] = unpack("v", fread($fh, 2)); // number of the disk with the start of the central directory 221 $eodir['total_entries_this'] = unpack("v", fread($fh, 2)); // total number of entries in the central dir on this disk 222 $eodir['total_entries'] = unpack("v", fread($fh, 2)); // total number of entries in 223 $eodir['size_of_cd'] = unpack("V", fread($fh, 4)); // size of the central directory 224 $eodir['offset_start_cd'] = unpack("V", fread($fh, 4)); // offset of start of central directory with respect to the starting disk number 225 $zipFileCommentLenght = unpack("v", fread($fh, 2)); // zipfile comment length 226 $eodir['zipfile_comment'] = $zipFileCommentLenght[1] ? fread($fh, $zipFileCommentLenght[1]) : ''; // zipfile comment 227 $this->endOfCentral = Array( 228 'disk_number_this' => $eodir['disk_number_this'][1], 229 'disk_number' => $eodir['disk_number'][1], 230 'total_entries_this' => $eodir['total_entries_this'][1], 231 'total_entries' => $eodir['total_entries'][1], 232 'size_of_cd' => $eodir['size_of_cd'][1], 233 'offset_start_cd' => $eodir['offset_start_cd'][1], 234 'zipfile_comment' => $eodir['zipfile_comment'], 235 ); 236 } else { 237 if (!$ddTry) { 238 $this->debugMsg(1, "Unexpected header. Trying to detect wrong placed 'Data Descriptor'...\n"); 239 $ddTry = true; 240 fseek($fh, 12 - 4, SEEK_CUR); // Jump over 'crc-32'(4) 'compressed-size'(4), 'uncompressed-size'(4) 241 continue; 242 } 243 $this->debugMsg(1, "Unexpected header, ending loop at offset " . ftell($fh)); 244 break; 245 } 246 $ddTry = false; 247 } 248 249 if ($this->debug) { 250 #------- Debug compressedList 251 $kkk = 0; 252 echo "<table border='0' style='font: 11px Verdana; border: 1px solid #000'>"; 253 foreach ($this->compressedList as $fileName => $item) { 254 if (!$kkk && $kkk = 1) { 255 echo "<tr style='background: #ADA'>"; 256 foreach ($item as $fieldName => $value) 257 echo "<td>$fieldName</td>"; 258 echo '</tr>'; 259 } 260 echo "<tr style='background: #CFC'>"; 261 foreach ($item as $fieldName => $value) { 262 if ($fieldName == 'lastmod_datetime') 263 echo "<td title='$fieldName' nowrap='nowrap'>" . date("d/m/Y H:i:s", $value) . "</td>"; 264 else 265 echo "<td title='$fieldName' nowrap='nowrap'>$value</td>"; 266 } 267 echo "</tr>"; 268 } 269 echo "</table>"; 270 271 #------- Debug centralDirList 272 $kkk = 0; 273 if (sizeof($this->centralDirList)) { 274 echo "<table border='0' style='font: 11px Verdana; border: 1px solid #000'>"; 275 foreach ($this->centralDirList as $fileName => $item) { 276 if (!$kkk && $kkk = 1) { 277 echo "<tr style='background: #AAD'>"; 278 foreach ($item as $fieldName => $value) 279 echo "<td>$fieldName</td>"; 280 echo '</tr>'; 281 } 282 echo "<tr style='background: #CCF'>"; 283 foreach ($item as $fieldName => $value) { 284 if ($fieldName == 'lastmod_datetime') 285 echo "<td title='$fieldName' nowrap='nowrap'>" . date("d/m/Y H:i:s", $value) . "</td>"; 286 else 287 echo "<td title='$fieldName' nowrap='nowrap'>$value</td>"; 288 } 289 echo "</tr>"; 290 } 291 echo "</table>"; 292 } 293 294 #------- Debug endOfCentral 295 $kkk = 0; 296 if (sizeof($this->endOfCentral)) { 297 echo "<table border='0' style='font: 11px Verdana' style='border: 1px solid #000'>"; 298 echo "<tr style='background: #DAA'><td colspan='2'>dUnzip - End of file</td></tr>"; 299 foreach ($this->endOfCentral as $field => $value) { 300 echo "<tr>"; 301 echo "<td style='background: #FCC'>$field</td>"; 302 echo "<td style='background: #FDD'>$value</td>"; 303 echo "</tr>"; 304 } 305 echo "</table>"; 306 } 307 } 308 309 return $this->compressedList; 310 } 311 312 313 Function getExtraInfo($compressedFileName) 314 { 315 return 316 isset($this->centralDirList[$compressedFileName]) ? 317 $this->centralDirList[$compressedFileName] : 318 false; 319 } 320 321 322 Function getZipInfo($detail = false) 323 { 324 return $detail ? 325 $this->endOfCentral[$detail] : 326 $this->endOfCentral; 327 } 328 329 330 Function unzip($compressedFileName, $targetFileName = false) 331 { 332 $fdetails = &$this->compressedList[$compressedFileName]; 333 334 if (!sizeof($this->compressedList)) { 335 $this->debugMsg(1, "Trying to unzip before loading file list... Loading it!"); 336 $this->getList(false, $compressedFileName); 337 } 338 if (!isset($this->compressedList[$compressedFileName])) { 339 $this->debugMsg(2, "File '<b>$compressedFileName</b>' is not compressed in the zip."); 340 return false; 341 } 342 if (substr($compressedFileName, -1) == "/") { 343 $this->debugMsg(2, "Trying to unzip a folder name '<b>$compressedFileName</b>'."); 344 return false; 345 } 346 if (!$fdetails['uncompressed_size']) { 347 $this->debugMsg(1, "File '<b>$compressedFileName</b>' is empty."); 348 return ""; 349 } 350 351 fseek($this->fh, $fdetails['contents-startOffset']); 352 return $this->uncompress( 353 fread($this->fh, $fdetails['compressed_size']), 354 $fdetails['compression_method'], 355 $fdetails['uncompressed_size']); 356 } 357 358 359 Function unzipAll($targetDir = false, $baseDir = "", $maintainStructure = true, $chmod = false) 360 { 361 if ($targetDir === false) 362 $targetDir = dirname(__FILE__) . "/"; 363 364 $lista = $this->getList(); 365 if (sizeof($lista)) foreach ($lista as $fileName => $trash) { 366 $dirname = dirname($fileName); 367 $outDN = "$targetDir/$dirname"; 368 369 if (substr($dirname, 0, strlen($baseDir)) != $baseDir) 370 continue; 371 372 if (!is_dir($outDN) && $maintainStructure) { 373 $str = ""; 374 $folders = explode("/", $dirname); 375 foreach ($folders as $folder) { 376 $str = $str ? "$str/$folder" : $folder; 377 if (!is_dir("$targetDir/$str")) { 378 $this->debugMsg(1, "Creating folder: $targetDir/$str"); 379 mkdir("$targetDir/$str"); 380 if ($chmod) 381 chmod("$targetDir/$str", $chmod); 382 } 383 } 384 } 385 if (substr($fileName, -1, 1) == "/") 386 continue; 387 388 $maintainStructure ? 389 $this->unzip($fileName, "$targetDir/$fileName") : 390 $this->unzip($fileName, "$targetDir/" . basename($fileName)); 391 392 if ($chmod) 393 chmod($maintainStructure ? "$targetDir/$fileName" : "$targetDir/" . basename($fileName), $chmod); 394 } 395 } 396 397 Function close() 398 { // Free the file resource 399 if ($this->fh) 400 fclose($this->fh); 401 } 402 403 // Private (you should NOT call these methods): 404 Function uncompress($content, $mode, $uncompressedSize, $targetFileName = false) 405 { 406 switch ($mode) { 407 case 0: 408 // Not compressed 409 return $content; 410 case 1: 411 $this->debugMsg(2, "Shrunk mode is not supported... yet?"); 412 return false; 413 case 2: 414 case 3: 415 case 4: 416 case 5: 417 $this->debugMsg(2, "Compression factor " . ($mode - 1) . " is not supported... yet?"); 418 return false; 419 case 6: 420 $this->debugMsg(2, "Implode is not supported... yet?"); 421 return false; 422 case 7: 423 $this->debugMsg(2, "Tokenizing compression algorithm is not supported... yet?"); 424 return false; 425 case 8: 426 // Deflate 427 return gzinflate($content, $uncompressedSize); 428 case 9: 429 $this->debugMsg(2, "Enhanced Deflating is not supported... yet?"); 430 return false; 431 case 10: 432 $this->debugMsg(2, "PKWARE Date Compression Library Impoloding is not supported... yet?"); 433 return false; 434 default: 435 $this->debugMsg(2, "Unknown uncompress method: $mode"); 436 return false; 437 } 438 } 439 440 441 Function debugMsg($level, $string) 442 { 443 if ($this->debug) 444 if ($level == 1) 445 echo "<b style='color: #777'>dUnzip2:</b> $string<br>"; 446 if ($level == 2) 447 echo "<b style='color: #F00'>dUnzip2:</b> $string<br>"; 448 } 449 } 450 451 ?>