* For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace PhpZip\Model\Extra\Fields; use PhpZip\Exception\ZipException; use PhpZip\Model\Extra\ZipExtraField; use PhpZip\Model\ZipEntry; /** * Info-ZIP New Unix Extra Field: * ====================================. * * Currently stores Unix UIDs/GIDs up to 32 bits. * (Last Revision 20080509) * * Value Size Description * ----- ---- ----------- * (UnixN) 0x7875 Short tag for this extra block type ("ux") * TSize Short total data size for this block * Version 1 byte version of this extra field, currently 1 * UIDSize 1 byte Size of UID field * UID Variable UID for this entry * GIDSize 1 byte Size of GID field * GID Variable GID for this entry * * Currently Version is set to the number 1. If there is a need * to change this field, the version will be incremented. Changes * may not be backward compatible so this extra field should not be * used if the version is not recognized. * * UIDSize is the size of the UID field in bytes. This size should * match the size of the UID field on the target OS. * * UID is the UID for this entry in standard little endian format. * * GIDSize is the size of the GID field in bytes. This size should * match the size of the GID field on the target OS. * * GID is the GID for this entry in standard little endian format. * * If both the old 16-bit Unix extra field (tag 0x7855, Info-ZIP Unix) * and this extra field are present, the values in this extra field * supercede the values in that extra field. */ final class NewUnixExtraField implements ZipExtraField { /** @var int header id */ public const HEADER_ID = 0x7875; /** ID of the first non-root user created on a unix system. */ public const USER_GID_PID = 1000; /** @var int version of this extra field, currently 1 */ private int $version; /** @var int User id */ private int $uid; /** @var int Group id */ private int $gid; public function __construct(int $version = 1, int $uid = self::USER_GID_PID, int $gid = self::USER_GID_PID) { $this->version = $version; $this->uid = $uid; $this->gid = $gid; } /** * Returns the Header ID (type) of this Extra Field. * The Header ID is an unsigned short integer (two bytes) * which must be constant during the life cycle of this object. */ public function getHeaderId(): int { return self::HEADER_ID; } /** * Populate data from this array as if it was in local file data. * * @param string $buffer the buffer to read data from * @param ZipEntry|null $entry optional zip entry * * @throws ZipException * * @return NewUnixExtraField */ public static function unpackLocalFileData(string $buffer, ?ZipEntry $entry = null): self { $length = \strlen($buffer); if ($length < 3) { throw new ZipException(sprintf('X7875_NewUnix length is too short, only %s bytes', $length)); } $offset = 0; [ 'version' => $version, 'uidSize' => $uidSize, ] = unpack('Cversion/CuidSize', $buffer); $offset += 2; $gid = self::readSizeIntegerLE(substr($buffer, $offset, $uidSize), $uidSize); $offset += $uidSize; $gidSize = unpack('C', $buffer[$offset])[1]; $offset++; $uid = self::readSizeIntegerLE(substr($buffer, $offset, $gidSize), $gidSize); return new self($version, $gid, $uid); } /** * Populate data from this array as if it was in central directory data. * * @param string $buffer the buffer to read data from * @param ZipEntry|null $entry optional zip entry * * @throws ZipException * * @return NewUnixExtraField */ public static function unpackCentralDirData(string $buffer, ?ZipEntry $entry = null): self { return self::unpackLocalFileData($buffer, $entry); } /** * The actual data to put into local file data - without Header-ID * or length specifier. * * @return string the data */ public function packLocalFileData(): string { return pack( 'CCVCV', $this->version, 4, // UIDSize $this->uid, 4, // GIDSize $this->gid ); } /** * The actual data to put into central directory - without Header-ID or * length specifier. * * @return string the data */ public function packCentralDirData(): string { return $this->packLocalFileData(); } /** * @throws ZipException */ private static function readSizeIntegerLE(string $data, int $size): int { $format = [ 1 => 'C', // unsigned byte 2 => 'v', // unsigned short LE 4 => 'V', // unsigned int LE ]; if (!isset($format[$size])) { throw new ZipException(sprintf('Invalid size bytes: %d', $size)); } return unpack($format[$size], $data)[1]; } public function getUid(): int { return $this->uid; } public function setUid(int $uid): void { $this->uid = $uid & 0xffffffff; } public function getGid(): int { return $this->gid; } public function setGid(int $gid): void { $this->gid = $gid & 0xffffffff; } public function getVersion(): int { return $this->version; } public function __toString(): string { return sprintf( '0x%04x NewUnix: UID=%d GID=%d', self::HEADER_ID, $this->uid, $this->gid ); } }