/** * Save the phar to disk. * * @param Pharchive $phar The phar to write. * * @param string $filename The file name to write to. * * @return void * * @throws \RuntimeException When the stub is illegal. */ public function save($phar, $filename) { $this->phar = $phar; $this->file = new StreamWriter($filename); $this->bins = new StreamWriter('php://temp'); $stub = $phar->getStub(); if (false === ($pos = strpos($stub, '__HALT_COMPILER();'))) { throw new \RuntimeException('Illegal stub'); } // Mimic plain PHP phar writing, it adds the closing tag. $this->file->write(substr($stub, 0, $pos + 18) . " ?>\r\n"); $this->buildManifest(); $this->file->writeStream($this->bins->seek(0)); unset($this->bins); // Now sign the phar. if ($phar->isSigned()) { $this->file->savePosition(); $this->file->seek(0); $hash = $this->file->hashStream($phar->getSignatureAlgorithm(), true); $this->file->loadPosition(); $this->file->write($hash); $this->file->writeUint32le($phar->getSignatureFlags()); $this->file->write('GBMB'); } unset($this->phar); unset($this->file); }
/** * Read the header from the file. * * @return void * * @throws \RuntimeException When the manifest is larger than 100MB (hardcoded limit in PHP). * * @throws \UnexpectedValueException When the manifest header is truncated. * * @throws \RuntimeException When the API version is not understood. * * @throws \UnexpectedValueException When the file length is smaller than the offset of the file contents. */ private function readHead() { $this->detectStub(); // Check header. $manifestLength = $this->file->readUint32le(); if ($manifestLength > 1048576 * 100) { // Prevent serious memory issues by limiting manifest to at most 100 MB in length. // See also: https://github.com/php/php-src/blob/12ff95/ext/phar/phar.c#L719 throw new \RuntimeException('manifest cannot be larger than 100 MB'); } $this->file->savePosition(); if ($manifestLength < 10 || $manifestLength != strlen($this->file->read($manifestLength))) { throw new \UnexpectedValueException('internal corruption of phar (truncated manifest header)'); } // Back to where we took off. $this->file->loadPosition(); $this->numFiles = $this->file->readUint32le(); $apiVersion = $this->file->readUint16be(); if (($apiVersion & 0xfff0) > self::MAX_API_VERSION) { throw new \RuntimeException(sprintf('Unable to process phar file, unsupported API version ', $apiVersion >> 12, $apiVersion >> 8 & 0xf, $apiVersion >> 4 & 0xf)); } $this->phar->setApiVersion($apiVersion); $this->phar->setFlags($this->file->readUint32le()); if (0 < ($aliasLength = $this->file->readUint32le())) { $this->phar->setAlias($this->file->read($aliasLength)); } if (0 < ($metadataLength = $this->file->readUint32le())) { $this->phar->setMetadata(unserialize($this->file->read($metadataLength))); } $this->binOffset = strlen($this->phar->getStub()) + $manifestLength + 4; if ($this->file->getLength() < $this->binOffset) { throw new \UnexpectedValueException('internal corruption of phar (truncated manifest header)'); } }