* [file header 1] * . * . * . * [file header n] * [digital signature] <-- Optional, TODO: check if implemented. * * * The Central Directory Header has the following structure: * *
 * + ---+---------+------------+------------------------+------------------------------------+
 * | ID | Offset  | Field size | Property               |  Description                       |
 * +----+---------+------------+------------------------+------------------------------------+
 * |    |  0      | 4          | -                      | Central directory header signature |
 * |    |  4      | 2          | versionMadeBy          | Version made by                    |
 * |    |  6      | 2          | versionNeededToExtract | Version needed to extract          |
 * |    |  8      | 2          | bitFlag                | General purpose bit flag           |
 * |    |  10     | 2          | compressionMethod      | Compression method                 |
 * |    |  12     | 2          | lastModFileTime        | Last modification file time        |
 * |    |  14     | 2          | lastModFileDate        | Last modification file date        |
 * |    |  16     | 4          | crc                    | crc-32                             |
 * |    |  20     | 4          | compressedSize         | compressed size                    |
 * |    |  24     | 4          | uncompressedSize       | uncompressed size                  |
 * | X: |  26     | 2          | fileNameLength         | file name length                   |
 * | Y: |  28     | 2          | extraFieldLength       | extra field length                 |
 * | Z: |  30     | 2          | fileCommentLength      | file comment length                |
 * |    |  32     | 2          | diskNumberStart        | disk number start                  |
 * |    |  34     | 2          | internalFileAttributes | internal file attributes           |
 * |    |  38     | 4          | externalFileAttributes | external file attributes           |
 * |    |  42     | 4          | relativeHeaderOffset   | relative offset of local header    |
 * |    |  46     | X          | fileName               | file name                          |
 * |    |  46+X   | Y          | -                      | extra field                        |
 * |    |  46+X+Y | Z          | comment                | file comment                       |
 * +----+---------+------------+------------------------+------------------------------------+
 * 
* * The columns of the table are: * - ID gives a label to a specific field or row in the table. * - Offset describes the start position of a header field. * - Field size describes the size of the field in bytes. * - Property is the name of the property that will be set by the header field. * - Description explains what this field describes. * * The central directory signature cannot be changed and is set in the constant {@link self::magic}. * * The extra fields that are implemented and references to extra documentation can be found in * the {@link ezcArchivelocalFileHeader}. * * @package Archive * @version 1.4.1 * @access private */ class ezcArchiveCentralDirectoryHeader extends ezcArchiveLocalFileHeader { /** * Defines the signature of this header. */ const magic = 0x02014b50; /** * Creates and initializes a new header. * * If the ezcArchiveCharacterFile $file is null then the header will be empty. * When an ezcArchiveCharacterFile is given, the file position should be directly after the * signature of the header. This header will be read from the file and initialized in this class. * * @param ezcArchiveCharacterFile $file */ public function __construct( ezcArchiveCharacterFile $file = null ) { if ( !is_null( $file ) ) { $this->properties = unpack ( "vversionMadeBy/". "vversionNeededToExtract/". "vbitFlag/". "vcompressionMethod/". "vlastModFileTime/". "vlastModFileDate/". "Vcrc/". "VcompressedSize/". "VuncompressedSize/". "vfileNameLength/". "vextraFieldLength/". "vfileCommentLength/". "vdiskNumberStart/". "vinternalFileAttributes/". "VexternalFileAttributes/". "VrelativeHeaderOffset", $file->read( 42 ) ); $this->properties["fileName"] = $file->read( $this->properties["fileNameLength"] ); $extraField = $file->read( $this->properties["extraFieldLength"] ); // FIXME, extra fields. $this->properties["comment"] = $file->read( $this->properties["fileCommentLength"] ); // Append extra field information. $this->setExtraFieldData( $extraField ); } else { // Some default values: $this->properties["versionMadeBy"] = 791; $this->properties["versionNeededToExtract"] = 10; $this->properties["diskNumberStart"] = 0; $this->setComment( "" ); } } /** * Sets the property $name to $value. * * @throws ezcBasePropertyNotFoundException if the property does not exist * @throws ezcBasePropertyReadOnlyException if the property is read-only * @param string $name * @param mixed $value * @return void * @ignore */ public function __set( $name, $value ) { switch ( $name ) { case "versionMadeBy": case "versionNeededToExtract": case "bitFlag": case "compressionMethod": case "lastModFileTime": case "lastModFileDate": case "crc": case "compressedSize": case "uncompressedSize": case "diskNumberStart": case "internalFileAttributes": case "externalFileAttributes": case "fileName": case "relativeHeaderOffset": $this->properties[$name] = $value; break; case "comment": $this->setComment( $value ); break; case "fileNameLength": case "extraFieldLength": case "fileCommentLength": throw new ezcBasePropertyReadOnlyException( $name ); default: throw new ezcBasePropertyNotFoundException( $name ); } } /** * Returns the value of the property $name. * * @throws ezcBasePropertyNotFoundException if the property does not exist. * @param string $name * @return mixed * @ignore */ public function __get( $name ) { switch ( $name ) { case "versionMadeBy": case "versionNeededToExtract": case "bitFlag": case "compressionMethod": case "lastModFileTime": case "lastModFileDate": case "crc": case "compressedSize": case "uncompressedSize": case "diskNumberStart": case "internalFileAttributes": case "externalFileAttributes": case "relativeHeaderOffset": case "fileNameLength": case "extraFieldLength": case "fileCommentLength": case "fileName": case "comment": return $this->properties[$name]; default: throw new ezcBasePropertyNotFoundException( $name ); } } /** * Sets the comment to string $comment and updates the comment length. * * @param string $comment * @return void */ public function setComment( $comment ) { $this->properties["comment"] = $comment; $this->properties["fileCommentLength"] = strlen( $comment ); } /** * Sets the type to int $type. * * The type is a constant from the {@link ezcArchiveEntry}. For example: * ezcArchiveEntry::IS_FIFO. * The property externalFileAttributes will be changed to reflect the $type. * * @throws ezcArchiveException * if $type is unknown * @param int $type * @return void */ public function setType( $type ) { $ext = 0; switch ( $type ) { case ezcArchiveEntry::IS_FIFO: $ext = ezcArchiveStatMode::S_IFIFO; break; case ezcArchiveEntry::IS_CHARACTER_DEVICE: $ext = ezcArchiveStatMode::S_IFCHR; break; case ezcArchiveEntry::IS_DIRECTORY: $ext = ezcArchiveStatMode::S_IFDIR; break; case ezcArchiveEntry::IS_BLOCK_DEVICE: $ext = ezcArchiveStatMode::S_IFBLK; break; case ezcArchiveEntry::IS_FILE: $ext = ezcArchiveStatMode::S_IFREG; break; case ezcArchiveEntry::IS_SYMBOLIC_LINK: $ext = ezcArchiveStatMode::S_IFLNK; break; default: throw new ezcArchiveException( "Unknown type" ); } if ( !isset( $this->properties["externalFileAttributes"] ) ) { $this->properties["externalFileAttributes"] = 0; } $ext <<= 16; $clear = ( $this->properties["externalFileAttributes"] & ezcArchiveStatMode::S_IFMT << 16 ); $this->properties["externalFileAttributes"] ^= $clear; // Remove the bits from S_IFMT, because we XOR with self. $this->properties["externalFileAttributes"] |= $ext; // And add the ext bits. } /** * Returns the type of the file. * * The type is read and translated from the externalFileAttributes property. * * @return int An ezcArchiveEntry constant. */ public function getType() { $extAttrib = ( $this->properties["externalFileAttributes"] >> 16 ); switch ( $extAttrib & ezcArchiveStatMode::S_IFMT ) { case ezcArchiveStatMode::S_IFIFO: return ezcArchiveEntry::IS_FIFO; case ezcArchiveStatMode::S_IFCHR: return ezcArchiveEntry::IS_CHARACTER_DEVICE; case ezcArchiveStatMode::S_IFDIR: return ezcArchiveEntry::IS_DIRECTORY; case ezcArchiveStatMode::S_IFBLK: return ezcArchiveEntry::IS_BLOCK_DEVICE; case ezcArchiveStatMode::S_IFREG: return ezcArchiveEntry::IS_FILE; case ezcArchiveStatMode::S_IFLNK: return ezcArchiveEntry::IS_SYMBOLIC_LINK; default: if ( substr( $this->properties["fileName"], -1 ) == "/" ) { return ezcArchiveEntry::IS_DIRECTORY; } else { return ezcArchiveEntry::IS_FILE; } } } /** * Returns the permissions (as a decimal number) of the file. * * The type is read and translated from the externalFileAttributes property. * * @return int */ public function getPermissions() { $extAttrib = ( $this->properties["externalFileAttributes"] >> 16 ); return decoct( $extAttrib & ezcArchiveStatMode::S_PERM_MASK ); } /** * Sets the permissions (as a decimal number) of the file to int $permissions. * * The $permissions expects an decimal number. * * The externalFileAttributes property will be changed. * * @param int $permissions */ public function setPermissions( $permissions ) { $perm = octdec( $permissions ); $perm <<= 16; if ( !isset( $this->properties["externalFileAttributes"] ) ) { $this->properties["externalFileAttributes"] = 0; } $clear = ( $this->properties["externalFileAttributes"] & ezcArchiveStatMode::S_PERM_MASK << 16 ); $this->properties["externalFileAttributes"] ^= $clear; // Remove the bits from S_PERM_MASK, because we XOR with self. $this->properties["externalFileAttributes"] |= $perm; // And add the perm bits. } /** * Updates the given ezcArchiveFileStructure $struct with the values from this header. * * If bool $override is false, this method will not overwrite the values from the ezcArchiveFileStructure $struct. * The values that can be set in the archiveFileStructure are: path, mode, type, size. * * @param ezcArchiveFileStructure &$struct * @param bool $override * @return void */ public function setArchiveFileStructure( ezcArchiveFileStructure &$struct, $override = false ) { if ( !isset( $struct->path ) || $override ) { $struct->path = $this->fileName; } if ( !isset( $struct->mode ) || $override ) { $struct->mode = ( $this->getPermissions() == 0 ? false : $this->getPermissions() ); } if ( !isset( $struct->type ) || $override ) { $struct->type = $this->getType(); } if ( !isset( $struct->size ) || $override ) { $struct->size = $this->uncompressedSize; } } /** * Returns the total size of this header. * * @return int */ public function getHeaderSize() { return 46 + $this->properties["fileNameLength"] + $this->properties["extraFieldLength"] + $this->properties["fileCommentLength"]; } /** * Sets this header with the values from the {@link ezcArchiveLocalFileHeader}. * * The properties that are set: versionNeededToExtract, bitFlag, compressionMethod, * lastModFileTime, lastModFileDate, crc, compressedSize, uncompressedSize, fileNameLength, * and fileName. * * @param ezcArchiveLocalFileHeader $localFileHeader * @return void */ public function setHeaderFromLocalFileHeader( ezcArchiveLocalFileHeader $localFileHeader ) { $this->properties["versionNeededToExtract"] = $localFileHeader->version; $this->properties["bitFlag"] = $localFileHeader->bitFlag; $this->properties["compressionMethod"] = $localFileHeader->compressionMethod; $this->properties["lastModFileTime"] = $localFileHeader->lastModFileTime; $this->properties["lastModFileDate"] = $localFileHeader->lastModFileDate; $this->properties["crc"] = $localFileHeader->crc; $this->properties["compressedSize"] = $localFileHeader->compressedSize; $this->properties["uncompressedSize"] = $localFileHeader->uncompressedSize; $this->properties["fileNameLength"] = $localFileHeader->fileNameLength; $this->properties["fileName"] = $localFileHeader->fileName; } /** * Sets this header with the values from the ezcArchiveEntry $entry. * * The values that are possible to set from the ezcArchiveEntry $entry are set in this header. * The properties that change are: internalFileAttributes, relativeHeaderOffset, type, and mtime. * * @param ezcArchiveEntry $entry * @return void */ public function setHeaderFromArchiveEntry( ezcArchiveEntry $entry ) { $this->properties["internalFileAttributes"] = 0; $this->setType( $entry->getType() ); $this->setPermissions( $entry->getPermissions() ); $this->properties["relativeHeaderOffset"] = 0; $this->properties["mtime"] = $entry->getModificationTime(); } /** * Serializes this header and appends it to the given ezcArchiveCharacterFile $archiveFile. * * @param ezcArchiveCharacterFile $archiveFile * @return void */ public function writeEncodedHeader( $archiveFile ) { $this->properties["extraFieldLength" ] = 13; // 9 + 4. $enc = pack( "VvvvvvvVVVvvvvvVV", self::magic, // V magic number $this->versionMadeBy, // v $this->versionNeededToExtract, // v $this->bitFlag, // v $this->compressionMethod, // v $this->lastModFileTime, // v $this->lastModFileDate, // v $this->crc, // V $this->compressedSize, // V $this->uncompressedSize, // V $this->fileNameLength, // v $this->extraFieldLength, // v extra data. $this->fileCommentLength , // v Comment $this->diskNumberStart , // v disknumber start $this->internalFileAttributes, // v Internal attribute $this->externalFileAttributes, // V external attributes $this->relativeHeaderOffset // V relative header offset? ); $time = pack( "vvcV", self::EF_TIME, 5, 1, $this->mtime ); // fixme atime? $unix2 = pack( "vv", self::EF_IZUNIX2, 0 ); // Add empty unix2 stamp. $archiveFile->write( $enc . $this->fileName . $time . $unix2 . $this->comment ); } /** * Returns true if the given string $string matches with the current signature. * * @param string $string * @return bool */ public static function isSignature( $string ) { return $string == pack( "V", self::magic ); } } ?>