Chris@18: fileName = $fileName; Chris@18: $this->fileType = $this->determineFileType(); Chris@18: } Chris@18: Chris@18: /** Chris@18: * @return Container Chris@18: */ Chris@18: public function resolveContainer() Chris@18: { Chris@18: $data = $this->extractData($this->resolveStream() . $this->fileName); Chris@18: Chris@18: if ($data['stubContent'] === null) { Chris@18: throw new ReaderException( Chris@18: 'Cannot resolve stub', Chris@18: 1547807881 Chris@18: ); Chris@18: } Chris@18: if ($data['manifestContent'] === null || $data['manifestLength'] === null) { Chris@18: throw new ReaderException( Chris@18: 'Cannot resolve manifest', Chris@18: 1547807882 Chris@18: ); Chris@18: } Chris@18: if (strlen($data['manifestContent']) < $data['manifestLength']) { Chris@18: throw new ReaderException( Chris@18: sprintf( Chris@18: 'Exected manifest length %d, got %d', Chris@18: strlen($data['manifestContent']), Chris@18: $data['manifestLength'] Chris@18: ), Chris@18: 1547807883 Chris@18: ); Chris@18: } Chris@18: Chris@18: return new Container( Chris@18: Stub::fromContent($data['stubContent']), Chris@18: Manifest::fromContent($data['manifestContent']) Chris@18: ); Chris@18: } Chris@18: Chris@18: /** Chris@18: * @param string $fileName e.g. '/path/file.phar' or 'compress.zlib:///path/file.phar' Chris@18: * @return array Chris@18: */ Chris@18: private function extractData($fileName) Chris@18: { Chris@18: $stubContent = null; Chris@18: $manifestContent = null; Chris@18: $manifestLength = null; Chris@18: Chris@18: $resource = fopen($fileName, 'r'); Chris@18: if (!is_resource($resource)) { Chris@18: throw new ReaderException( Chris@18: sprintf('Resource %s could not be opened', $fileName), Chris@18: 1547902055 Chris@18: ); Chris@18: } Chris@18: Chris@18: while (!feof($resource)) { Chris@18: $line = fgets($resource); Chris@18: // stop reading file when manifest can be extracted Chris@18: if ($manifestLength !== null && $manifestContent !== null && strlen($manifestContent) >= $manifestLength) { Chris@18: break; Chris@18: } Chris@18: Chris@18: $manifestPosition = strpos($line, '__HALT_COMPILER();'); Chris@18: Chris@18: // first line contains start of manifest Chris@18: if ($stubContent === null && $manifestContent === null && $manifestPosition !== false) { Chris@18: $stubContent = substr($line, 0, $manifestPosition - 1); Chris@18: $manifestContent = preg_replace('#^.*__HALT_COMPILER\(\);(?>[ \n]\?>(?>\r\n|\n)?)?#', '', $line); Chris@18: $manifestLength = $this->resolveManifestLength($manifestContent); Chris@18: // line contains start of stub Chris@18: } elseif ($stubContent === null) { Chris@18: $stubContent = $line; Chris@18: // line contains start of manifest Chris@18: } elseif ($manifestContent === null && $manifestPosition !== false) { Chris@18: $manifestContent = preg_replace('#^.*__HALT_COMPILER\(\);(?>[ \n]\?>(?>\r\n|\n)?)?#', '', $line); Chris@18: $manifestLength = $this->resolveManifestLength($manifestContent); Chris@18: // manifest has been started (thus is cannot be stub anymore), add content Chris@18: } elseif ($manifestContent !== null) { Chris@18: $manifestContent .= $line; Chris@18: $manifestLength = $this->resolveManifestLength($manifestContent); Chris@18: // stub has been started (thus cannot be manifest here, yet), add content Chris@18: } elseif ($stubContent !== null) { Chris@18: $stubContent .= $line; Chris@18: } Chris@18: } Chris@18: fclose($resource); Chris@18: Chris@18: return array( Chris@18: 'stubContent' => $stubContent, Chris@18: 'manifestContent' => $manifestContent, Chris@18: 'manifestLength' => $manifestLength, Chris@18: ); Chris@18: } Chris@18: Chris@18: /** Chris@18: * Resolves stream in order to handle compressed Phar archives. Chris@18: * Chris@18: * @return string Chris@18: */ Chris@18: private function resolveStream() Chris@18: { Chris@18: if ($this->fileType === 'application/x-gzip') { Chris@18: return 'compress.zlib://'; Chris@18: } elseif ($this->fileType === 'application/x-bzip2') { Chris@18: return 'compress.bzip2://'; Chris@18: } Chris@18: return ''; Chris@18: } Chris@18: Chris@18: /** Chris@18: * @return string Chris@18: */ Chris@18: private function determineFileType() Chris@18: { Chris@18: $fileInfo = new \finfo(); Chris@18: return $fileInfo->file($this->fileName, FILEINFO_MIME_TYPE); Chris@18: } Chris@18: Chris@18: /** Chris@18: * @param string $content Chris@18: * @return int|null Chris@18: */ Chris@18: private function resolveManifestLength($content) Chris@18: { Chris@18: if (strlen($content) < 4) { Chris@18: return null; Chris@18: } Chris@18: return static::resolveFourByteLittleEndian($content, 0); Chris@18: } Chris@18: Chris@18: /** Chris@18: * @param string $content Chris@18: * @param int $start Chris@18: * @return int Chris@18: */ Chris@18: public static function resolveFourByteLittleEndian($content, $start) Chris@18: { Chris@18: $payload = substr($content, $start, 4); Chris@18: if (!is_string($payload)) { Chris@18: throw new ReaderException( Chris@18: sprintf('Cannot resolve value at offset %d', $start), Chris@18: 1539614260 Chris@18: ); Chris@18: } Chris@18: Chris@18: $value = unpack('V', $payload); Chris@18: if (!isset($value[1])) { Chris@18: throw new ReaderException( Chris@18: sprintf('Cannot resolve value at offset %d', $start), Chris@18: 1539614261 Chris@18: ); Chris@18: } Chris@18: return $value[1]; Chris@18: } Chris@18: Chris@18: /** Chris@18: * @param string $content Chris@18: * @param int $start Chris@18: * @return int Chris@18: */ Chris@18: public static function resolveTwoByteBigEndian($content, $start) Chris@18: { Chris@18: $payload = substr($content, $start, 2); Chris@18: if (!is_string($payload)) { Chris@18: throw new ReaderException( Chris@18: sprintf('Cannot resolve value at offset %d', $start), Chris@18: 1539614263 Chris@18: ); Chris@18: } Chris@18: Chris@18: $value = unpack('n', $payload); Chris@18: if (!isset($value[1])) { Chris@18: throw new ReaderException( Chris@18: sprintf('Cannot resolve value at offset %d', $start), Chris@18: 1539614264 Chris@18: ); Chris@18: } Chris@18: return $value[1]; Chris@18: } Chris@18: }