<?php /** * * @author A. Grandt <php@grandt.com> * * Classes to assist in handling the header structures in Zip Files. * */ namespace ZipMerge\Zip\Core\Header; use com\grandt\BinStringStatic; use ZipMerge\Zip\Core\ExtraField\AbstractExtraField; use ZipMerge\Zip\Core\ExtraField\ExtendedTimeStampExtraField; use ZipMerge\Zip\Core\ExtraField\GenericExtraField; use ZipMerge\Zip\Core\ExtraField\UnicodeCommentExtraField; use ZipMerge\Zip\Core\ZipUtils; class ZipFileEntry extends AbstractZipHeader { public $versionMadeBy = AbstractZipHeader::ATTR_MADE_BY_VERSION; public $versionNeeded = AbstractZipHeader::ATTR_VERSION_TO_EXTRACT; public $originalLFH = null; public $originalCDR = null; public $offset; public $dataOffset; public $gpFlags; public $gzType; public $dosTime; public $fileCRC32; public $gzLength; public $dataLength; public $entryEndOffset; public $path = ''; public $isDirectory = false; public $comment = ''; public $extraFieldsArray = array(); public $discNumberStart = AbstractZipHeader::NULL_WORD; public $internalFileAttributes = AbstractZipHeader::NULL_WORD; public $externalFileAttributes = AbstractZipHeader::NULL_DWORD; public function __construct($handle = null) { if ($handle != null) { $this->parseHeader($handle); } } public function parseHeader($handle) { $pk = fread($handle, 4); if ($pk == AbstractZipHeader::ZIP_LOCAL_FILE_HEADER) { /* * local file header signature 4 bytes (0x04034b50) * version needed to extract 2 bytes * general purpose bit flag 2 bytes * compression method 2 bytes * last mod file time + file date 4 bytes * crc-32 4 bytes * compressed size 4 bytes * uncompressed size 4 bytes * file name length 2 bytes * extra field length 2 bytes * --------------------------------------- * 30 bytes */ $this->offset = (int)ftell($handle) - 4; $this->versionNeeded = fread($handle, 2); $arr = unpack("v2wa", fread($handle, 4)); $this->gpFlags = $arr['wa1']; $this->gzType = $arr['wa2']; $this->dosTime = fread($handle, 4); $arr = unpack("V3dwa/v2wa", fread($handle, 16)); $this->fileCRC32 = $arr['dwa1']; $this->gzLength = $arr['dwa2']; $this->dataLength = $arr['dwa3']; $filePathLength = $arr['wa1']; $localExtraFieldLength = $arr['wa2']; $this->path = fread($handle, $filePathLength); $this->dataOffset = 30 + $filePathLength + $localExtraFieldLength; if ($localExtraFieldLength > 0) { $eoef = ftell($handle) + $localExtraFieldLength; while (ftell($handle) < $eoef) { $ef = AbstractExtraField::decodeField($handle); /* @var $ef AbstractExtraField */ $this->extraFieldsArray[$ef->header] = $ef; } } $hasDataDescriptor = $this->gpFlags & 4 == 4; if ($hasDataDescriptor) { $pkHeader = AbstractZipHeader::seekPKHeader($handle); if ($pkHeader !== self::ZIP_LOCAL_DATA_DESCRIPTOR) { fseek($handle, -12, SEEK_CUR); } $arr = unpack("Vdwa", fread($handle, 12)); $this->fileCRC32 = $arr['dwa1']; $this->gzLength = $arr['dwa2']; $this->dataLength = $arr['dwa3']; } else { fseek($handle, $this->gzLength, SEEK_CUR); } $this->isDirectory = !$hasDataDescriptor && $this->dataLength == 0 && $this->fileCRC32 == 0; } else if ($pk == AbstractZipHeader::ZIP_CENTRAL_FILE_HEADER) { /* * * central file header signature 4 bytes (0x02014b50) * version made by 2 bytes * version needed to extract 2 bytes * general purpose bit flag 2 bytes * compression method 2 bytes * last mod file time 2 bytes * last mod file date 2 bytes * crc-32 4 bytes * compressed size 4 bytes * uncompressed size 4 bytes * file name length 2 bytes * extra field length 2 bytes * file comment length 2 bytes * disk number start 2 bytes * internal file attributes 2 bytes * external file attributes 4 bytes * relative offset of local header 4 bytes * ---------------------------------------- * 46 bytes * * file name (variable size) * extra field (variable size) * file comment (variable size) */ // $this->offset = (int)ftell($handle) - 4; $this->versionMadeBy = fread($handle, 2); $this->versionNeeded = fread($handle, 2); $arr = unpack("v2wa", fread($handle, 4)); $this->gpFlags = $arr['wa1']; $this->gzType = $arr['wa2']; $this->dosTime = fread($handle, 4); $arr = unpack("V3dwa/v3wa", fread($handle, 18)); // $this->fileCRC32 = $arr['dwa1']; // $this->gzLength = $arr['dwa2']; // $this->dataLength = $arr['dwa3']; $filePathLength = $arr['wa1']; $centralExtraFieldLength = $arr['wa2']; $fileCommentLength = $arr['wa3']; $this->discNumberStart = fread($handle, 2); $this->internalFileAttributes = fread($handle, 2); $this->externalFileAttributes = fread($handle, 4); // $arr = unpack("V", fread($handle, 4)); // $this->relativeOffsetOfLocalHeader = $arr[1]; // $this->path = fread($handle, $filePathLength); fseek($handle, 4+$filePathLength, SEEK_CUR); if ($centralExtraFieldLength > 0) { $eoef = ftell($handle) + $centralExtraFieldLength; while (ftell($handle) < $eoef) { $ef = AbstractExtraField::decodeField($handle, false); /* @var $ef AbstractExtraField */ if (in_array($ef->header, $this->extraFieldsArray)) { $ef2 = $this->extraFieldsArray[$ef->header]; $ef2->centralData = $ef->centralData; } else { $this->extraFieldsArray[$ef->header] = $ef; } } } if ($fileCommentLength > 0) { $this->comment = fread($handle, $fileCommentLength); } } else { fseek($handle, -4, SEEK_CUR); } } public function addExtraField($extraField) { $this->extraFieldsArray[$extraField->header] = $extraField; } public function addPath($path) { $this->path = AbstractZipHeader::pathJoin($path, $this->path); if (in_array(AbstractExtraField::HEADER_UNICODE_PATH, $this->extraFieldsArray) && isset($this->extraFieldsArray[AbstractExtraField::HEADER_UNICODE_PATH])) { $ef = $this->extraFieldsArray[AbstractExtraField::HEADER_UNICODE_PATH]; $ef->CRC32 = crc32($this->path); $ef->utf8Data = AbstractZipHeader::pathJoin($path, $ef->utf8Data); } } public function getLocalHeader() { $ef = ''; foreach ($this->extraFieldsArray as $value) { /* @var $value AbstractExtraField */ $ef .= $value->getLocalField(); } $lf = AbstractZipHeader::ZIP_LOCAL_FILE_HEADER . $this->versionNeeded; $lf .= pack("vv", $this->gpFlags, $this->gzType); $lf .= $this->dosTime; $lf .= pack("VVV", $this->fileCRC32, $this->gzLength, $this->dataLength); $lf .= pack("v", BinStringStatic::_strlen($this->path)); $lf .= pack("v", BinStringStatic::_strlen($ef)); $lf .= $this->path; $lf .= $ef; return $lf; } public function getCentralDirectoryHeader() { $ef = ''; foreach ($this->extraFieldsArray as $value) { /* @var $value AbstractExtraField */ $ef .= $value->getCentralField(); } $cd = AbstractZipHeader::ZIP_CENTRAL_FILE_HEADER . $this->versionMadeBy . $this->versionNeeded; $cd .= pack("vv", $this->gpFlags, $this->gzType); $cd .= $this->dosTime; $cd .= pack("VVV", $this->fileCRC32, $this->gzLength, $this->dataLength); $cd .= pack("v", BinStringStatic::_strlen($this->path)); $cd .= pack("v", BinStringStatic::_strlen($ef)); $cd .= pack("v", BinStringStatic::_strlen($this->comment)); $cd .= $this->discNumberStart. $this->internalFileAttributes . $this->externalFileAttributes; $cd .= pack("V", $this->offset); $cd .= $this->path; $cd .= $ef; $cd .= $this->comment; return $cd; } public function prependPath($path) { $this->path = AbstractZipHeader::pathJoin($path, $this->path); if (in_array(AbstractExtraField::HEADER_UNICODE_PATH, $this->extraFieldsArray) && isset($this->extraFieldsArray[AbstractExtraField::HEADER_UNICODE_PATH])) { $ef = $this->extraFieldsArray[AbstractExtraField::HEADER_UNICODE_PATH]; $ef->CRC32 = crc32($this->path); $ef->utf8Data = AbstractZipHeader::pathJoin($path, $ef->utf8Data); } } public function setFileComment($comment) { $this->comment = $comment; if (in_array(AbstractExtraField::HEADER_UNICODE_COMMENT, $this->extraFieldsArray) && isset($this->extraFieldsArray[AbstractExtraField::HEADER_UNICODE_COMMENT])) { $ef = $this->extraFieldsArray[AbstractExtraField::HEADER_UNICODE_COMMENT]; $ef->CRC32 = crc32($this->comment); } } public function setUTF8FileComment($comment) { if (in_array(AbstractExtraField::HEADER_UNICODE_COMMENT, $this->extraFieldsArray) && isset($this->extraFieldsArray[AbstractExtraField::HEADER_UNICODE_COMMENT])) { $ef = $this->extraFieldsArray[AbstractExtraField::HEADER_UNICODE_COMMENT]; $ef->utf8Data = $comment; } else { $ef = new UnicodeCommentExtraField(); $ef->CRC32 = crc32($this->comment); $ef->utf8Data = $comment; $this->extraFieldsArray[AbstractExtraField::HEADER_UNICODE_COMMENT] = $ef; } } public static function createDirEntry($path, $timestamp) { $fileEntry = new ZipFileEntry(); $fileEntry->gzType = 0; $fileEntry->gpFlags = 0; $fileEntry->fileCRC32 = 0; $fileEntry->externalFileAttributes = ZipFileEntry::EXT_FILE_ATTR_DIR; $fileEntry->gzLength = 0; $fileEntry->dataLength = 0; $fileEntry->isDirectory = true; $fileEntry->dosTime = ZipUtils::getDosTime($timestamp); $fileEntry->path = $path; $ef = new ExtendedTimeStampExtraField(); $ef->setModTime($timestamp); $ef->setAccessTime($timestamp); $fileEntry->addExtraField($ef); $ef = new GenericExtraField(); $ef->header = AbstractExtraField::HEADER_UNIX_TYPE_3; $ef->setFieldData("\x01\x04\xE8\x03\x00\x00\x04\x00\x00\x00\x00", ''); $fileEntry->addExtraField($ef); return $fileEntry; } }