blob: 9319c5dfb24334b871123afa34731a35f87adab4 [file] [log] [blame]
<?php
/**
* File containing the ezcArchiveCentralDirectoryHeader class.
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
* @package Archive
* @version //autogentag//
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @access private
*/
/**
* The ezcArchiveCentralDirectoryHeader class represents the Zip central directory header.
*
* ezcArchiveCentralDirectoryHeader can read the header from an ezcArchiveCharacterFile or ezcArchiveEntry.
*
* The values from the headers are directly accessible via the class properties, and allows
* reading and writing to specific header values.
*
* The entire header can be appended to an ezcArchiveCharacterFile again or written to an ezcArchiveFileStructure.
* Information may get lost, though.
*
* The central directory format[1]:
*
* <pre>
* [file header 1]
* .
* .
* .
* [file header n]
* [digital signature] <-- Optional, TODO: check if implemented.
* </pre>
*
* The Central Directory Header has the following structure:
*
* <pre>
* + ---+---------+------------+------------------------+------------------------------------+
* | 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 |
* +----+---------+------------+------------------------+------------------------------------+
* </pre>
*
* 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 //autogentag//
* @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 );
}
}
?>