Kelanjutan dari siklus tentang arsip Zip dan PHP. Artikel sebelumnya: Bagian 1 , Bagian 2 , Bagian 3Selamat siang, pembaca yang budiman.
Kali ini saya ingin memperkenalkan, mungkin, bagian akhir dari siklus tentang arsip Zip dan PHP.
Pada artikel ini saya akan menunjukkan cara membaca arsip yang ada dan sebagai contoh kita akan mengambil
foto.zip dari
artikel sebelumnya . Agar tidak mengulangi semua prosedur, kami akan menggunakan yang sudah jadi -
https://github.com/userqq/images/raw/master/photos.zip .
Dan sekarang mari kita menyimpang sejenak dan ingat apa yang terdiri dari arsip kita: pertama ada satu set data file yang dikemas, di mana setiap file yang dikemas diawali oleh struktur Header File Lokal (LFH), setelah semua data kita memiliki satu set struktur Header File Direktori Utama (CDFH) - Ini adalah daftar isi dalam arsip kami, yang mencantumkan semua elemen dan posisi perpindahannya relatif ke awal file. Dan arsip Catatan Direktori Tengah (EOCD) selesai - di sini adalah posisi awal dari struktur CDFH, jumlah dan panjang total dalam byte. Oleh karena itu, arsip harus dibaca dari akhir, pertama untuk menemukan EOCD, kemudian membaca struktur CDFH dan dengan demikian mendapatkan daftar file dalam arsip.
FYI: Dan beberapa format, seperti JPEG, dibaca dari awal. Oleh karena itu, kita dapat merekatkan gambar dengan arsip, bahkan norak melalui cat image.jpeg archive.zip> imagearchive.jpeg , tanpa kehilangan fungsionalitas. Browser dan aplikasi untuk melihat gambar akan menunjukkan kepada kita gambar tanpa masalah. Sementara aplikasi apa pun untuk membaca arsip zip, baik itu 7z atau unzip, akan dapat bekerja dengan tenang dengan file sebagai arsip. Misalnya, di sini - https://github.com/userqq/images/blob/master/jpegarchive.jpg (Perhatian, benda ini beratnya sekitar 20mb, jadi saya tidak menyarankan membuka lalu lintas dari ponsel atau jika Anda peduli tentang lalu lintas). Jadi, jika Anda tahu hosting gambar yang gambarnya tidak dikode ulang dan tidak dipotong, Anda dapat mengunggah tidak hanya gambar di sana :) Meskipun, menurut saya, sekarang Anda tidak dapat menemukannya.Pertama, kita mendefinisikan fungsi pembantu yang akan memudahkan kita untuk bekerja dengan membongkar (Saya melihat ide ini pada
petunjuk di salah satu repositori untuk bekerja dengan kaus kaki dan memodifikasinya sedikit untuk kasus kita):
function readBytes($fh, $formatArray) {
Sekarang mari kita cari struktur EOCD. Panjang minimumnya, jika tidak ada komentar, akan menjadi 22 byte, dimana 4 pertama adalah tanda tangan. Oleh karena itu, pertama-tama kita pindah ke posisi fileize ($ file) - 22, baca 4 byte berikutnya, dan jika kita beruntung dan byte ini sama dengan tanda tangan (0x06054b50), maka ini adalah EOCD kita. Jika Anda tidak beruntung - byte-demi-bit kami pindah ke awal file sampai kami menemukan atau menyelesaikan file - maka, mungkin, ini bukan arsip.
$fh = fopen('photos.zip', 'r'); for ($offset = 22, $length = fstat($fh)['size']; $offset <= $length; $offset++) { fseek($fh, $offset * -1, SEEK_END);
Sekarang kita tahu berapa banyak elemen yang kita miliki di arsip dan di mana "daftar isi" dimulai - daftar struktur CDFH. Kita dapat mengulanginya dan mendapatkan nama, ukuran data, dan posisi awal LFH untuk masing-masing elemen arsip.
echo ' :' . PHP_EOL;
Secara umum, menjalankan file bolak-balik untuk masing-masing catatan bukanlah ide terbaik dalam hal kinerja, jadi saya akan merekomendasikan membaca semua struktur CDFH, dan kemudian membaca LFH dan data, jika perlu, tapi di sini kita memilikinya " Pemrograman yang tidak konvensional, ”jadi kami bisa.Kode skrip lengkap <?php function readBytes($fh, $formatArray) { static $lengths = ['L' => 4, 'l' => 4, 'i' => 4, 'I' => 4, 'S' => 2, 'a' => 1]; $totalLength = 0; $unpackFormat = []; $nullData = []; foreach ($formatArray as $name => $format) { $length = 1; if (is_array($format)) { [$format, $length] = $format; } if ($length < 1) { $nullData[] = $name; continue; } $totalLength += $lengths[$format] * $length; $unpackFormat[] = $format . (($length > 1) ? $length : '') . $name; } $packet = []; if ($totalLength > 0) { $packet = unpack(implode('/', $unpackFormat), fread($fh, $totalLength)); } foreach ($nullData as $empty) { $packet[$empty] = null; } return $packet; } $fh = fopen('photos.zip', 'r'); for ($offset = 22, $length = fstat($fh)['size']; $offset <= $length; $offset++) { fseek($fh, $offset * -1, SEEK_END); if ("\x50\x4b\x05\x06" === $bytes = fread($fh, 4)) { echo 'EOCD ' . ($length - $offset) . PHP_EOL; break; } } $EOCD = readBytes($fh, [ 'diskNumber' => 'S', 'startDiskNumber' => 'S', 'numberCentralDirectoryRecord' => 'S', 'totalCentralDirectoryRecord' => 'S', 'sizeOfCentralDirectory' => 'L', 'centralDirectoryOffset' => 'L', 'commentLength' => 'S', ]); echo ' : ' . $EOCD['numberCentralDirectoryRecord'] . PHP_EOL; echo ' CDFH: ' . $EOCD['centralDirectoryOffset'] . PHP_EOL; echo ' :' . PHP_EOL; $offset = $EOCD['centralDirectoryOffset']; for ($i = 0; $i < $EOCD['numberCentralDirectoryRecord']; $i++) { fseek($fh, $offset, SEEK_SET); if ("\x50\x4b\x01\x02" !== $bytes = fread($fh, 4)) { exit(' CDFH' . PHP_EOL); } $CDFH = readBytes($fh, [ 'versionMadeBy' => 'S', 'versionToExtract' => 'S', 'generalPurposeBitFlag' => 'S', 'compressionMethod' => 'S', 'modificationTime' => 'S', 'modificationDate' => 'S', 'crc32' => 'L', 'compressedSize' => 'L', 'uncompressedSize' => 'L', 'filenameLength' => 'S', 'extraFieldLength' => 'S', 'fileCommentLength' => 'S', 'diskNumber' => 'S', 'internalFileAttributes' => 'S', 'externalFileAttributes' => 'L', 'localFileHeaderOffset' => 'L', ]); $CDFH += readBytes($fh, [ 'filename' => ['a', $CDFH['filenameLength']], 'extraField' => ['a', $CDFH['extraFieldLength']], 'fileComment' => ['a', $CDFH['fileCommentLength']], ]); $offset = ftell($fh); fseek($fh, $CDFH['localFileHeaderOffset'], SEEK_SET); if ("\x50\x4b\x03\x04" !== $bytes = fread($fh, 4)) { exit(' LFH' . PHP_EOL); } $LFH = readBytes($fh, [ 'versionToExtract' => 'S', 'generalPurposeBitFlag' => 'S', 'compressionMethod' => 'S', 'modificationTime' => 'S', 'modificationDate' => 'S', 'crc32' => 'L', 'compressedSize' => 'L', 'uncompressedSize' => 'L', 'filenameLength' => 'S', 'extraFieldLength' => 'S', ]); $LFH += readBytes($fh, [ 'filename' => ['a', $LFH['filenameLength']], 'extraField' => ['a', $LFH['extraFieldLength']], ]); $dataOffset = ftell($fh); echo '> ' . $CDFH['filename'] . ' ' . $CDFH['compressedSize'] . ' , '; echo 'LFH: ' . $CDFH['localFileHeaderOffset'] . ', '; echo ' : ' . $dataOffset . ', '; echo ' : ' . ($dataOffset + $CDFH['compressedSize']); echo PHP_EOL; } fclose($fh);
Sebagai hasil dari skrip, kita harus mendapatkan informasi tentang arsip seperti berikut:
$ php readzip.php EOCD 18873702 : 172 CDFH: 18864696 : > 0.jpg 135021 , LFH: 0, : 35, : 135056 > 1.jpg 205686 , LFH: 135056, : 135091, : 340777 > 2.jpg 81393 , LFH: 340777, : 340812, : 422205 > 3.jpg 64892 , LFH: 422205, : 422240, : 487132 ... > 171.jpg 50465 , LFH: 18814194, : 18814231, : 18864696
Dan, secara umum, berkat data ini, kami sudah dapat mengekstrak semua atau file tertentu.
Kami melewati kompresi enkripsi dan hanya mempertimbangkan kasus ketika kami memiliki data dalam arsip sebagaimana adanya, tetapi ini tidak diperlukan untuk memahami struktur, dan bagi mereka yang ingin bingung, tidak akan sulit untuk membaca spesifikasi dan menambahkan fungsionalitas yang hilang berdasarkan seri artikel ini.
Oleh karena itu, saya pikir kita dapat mempertimbangkan siklus utama selesai. Jika Anda masih memiliki pertanyaan atau saya akan memanggil beberapa komentar di komentar untuk artikel sebelumnya, maka mungkin ada beberapa artikel tambahan.
Untuk semuanya sim.
Dan saya harap meskipun itu sedikit bagi Anda, itu masih menarik :)