123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249 |
- <?php
- namespace TYPO3\PharStreamWrapper\Resolver;
- /*
- * This file is part of the TYPO3 project.
- *
- * It is free software; you can redistribute it and/or modify it under the terms
- * of the MIT License (MIT). For the full copyright and license information,
- * please read the LICENSE file that was distributed with this source code.
- *
- * The TYPO3 project - inspiring people to share!
- */
- use TYPO3\PharStreamWrapper\Helper;
- use TYPO3\PharStreamWrapper\Manager;
- use TYPO3\PharStreamWrapper\Phar\Reader;
- use TYPO3\PharStreamWrapper\Phar\ReaderException;
- use TYPO3\PharStreamWrapper\Resolvable;
- class PharInvocationResolver implements Resolvable
- {
- const RESOLVE_REALPATH = 1;
- const RESOLVE_ALIAS = 2;
- const ASSERT_INTERNAL_INVOCATION = 32;
- /**
- * @var string[]
- */
- private $invocationFunctionNames = array(
- 'include',
- 'include_once',
- 'require',
- 'require_once'
- );
- /**
- * Contains resolved base names in order to reduce file IO.
- *
- * @var string[]
- */
- private $baseNames = array();
- /**
- * Resolves PharInvocation value object (baseName and optional alias).
- *
- * Phar aliases are intended to be used only inside Phar archives, however
- * PharStreamWrapper needs this information exposed outside of Phar as well
- * It is possible that same alias is used for different $baseName values.
- * That's why PharInvocationCollection behaves like a stack when resolving
- * base-name for a given alias. On the other hand it is not possible that
- * one $baseName is referring to multiple aliases.
- * @see https://secure.php.net/manual/en/phar.setalias.php
- * @see https://secure.php.net/manual/en/phar.mapphar.php
- *
- * @param string $path
- * @param int|null $flags
- * @return null|PharInvocation
- */
- public function resolve($path, $flags = null)
- {
- $hasPharPrefix = Helper::hasPharPrefix($path);
- if ($flags === null) {
- $flags = static::RESOLVE_REALPATH | static::RESOLVE_ALIAS;
- }
- if ($hasPharPrefix && $flags & static::RESOLVE_ALIAS) {
- $invocation = $this->findByAlias($path);
- if ($invocation !== null) {
- return $invocation;
- }
- }
- $baseName = $this->resolveBaseName($path, $flags);
- if ($baseName === null) {
- return null;
- }
- if ($flags & static::RESOLVE_REALPATH) {
- $baseName = $this->baseNames[$baseName];
- }
- return $this->retrieveInvocation($baseName, $flags);
- }
- /**
- * Retrieves PharInvocation, either existing in collection or created on demand
- * with resolving a potential alias name used in the according Phar archive.
- *
- * @param string $baseName
- * @param int $flags
- * @return PharInvocation
- */
- private function retrieveInvocation($baseName, $flags)
- {
- $invocation = $this->findByBaseName($baseName);
- if ($invocation !== null) {
- return $invocation;
- }
- if ($flags & static::RESOLVE_ALIAS) {
- $reader = new Reader($baseName);
- $alias = $reader->resolveContainer()->getAlias();
- } else {
- $alias = '';
- }
- // add unconfirmed(!) new invocation to collection
- $invocation = new PharInvocation($baseName, $alias);
- Manager::instance()->getCollection()->collect($invocation);
- return $invocation;
- }
- /**
- * @param string $path
- * @param int $flags
- * @return null|string
- */
- private function resolveBaseName($path, $flags)
- {
- $baseName = $this->findInBaseNames($path);
- if ($baseName !== null) {
- return $baseName;
- }
- $baseName = Helper::determineBaseFile($path);
- if ($baseName !== null) {
- $this->addBaseName($baseName);
- return $baseName;
- }
- $possibleAlias = $this->resolvePossibleAlias($path);
- if (!($flags & static::RESOLVE_ALIAS) || $possibleAlias === null) {
- return null;
- }
- $trace = debug_backtrace();
- foreach ($trace as $item) {
- if (!isset($item['function']) || !isset($item['args'][0])
- || !in_array($item['function'], $this->invocationFunctionNames, true)) {
- continue;
- }
- $currentPath = $item['args'][0];
- if (Helper::hasPharPrefix($currentPath)) {
- continue;
- }
- $currentBaseName = Helper::determineBaseFile($currentPath);
- if ($currentBaseName === null) {
- continue;
- }
- // ensure the possible alias name (how we have been called initially) matches
- // the resolved alias name that was retrieved by the current possible base name
- try {
- $reader = new Reader($currentBaseName);
- $currentAlias = $reader->resolveContainer()->getAlias();
- } catch (ReaderException $exception) {
- // most probably that was not a Phar file
- continue;
- }
- if (empty($currentAlias) || $currentAlias !== $possibleAlias) {
- continue;
- }
- $this->addBaseName($currentBaseName);
- return $currentBaseName;
- }
- return null;
- }
- /**
- * @param string $path
- * @return null|string
- */
- private function resolvePossibleAlias($path)
- {
- $normalizedPath = Helper::normalizePath($path);
- return strstr($normalizedPath, '/', true) ?: null;
- }
- /**
- * @param string $baseName
- * @return null|PharInvocation
- */
- private function findByBaseName($baseName)
- {
- return Manager::instance()->getCollection()->findByCallback(
- function (PharInvocation $candidate) use ($baseName) {
- return $candidate->getBaseName() === $baseName;
- },
- true
- );
- }
- /**
- * @param string $path
- * @return null|string
- */
- private function findInBaseNames($path)
- {
- // return directly if the resolved base name was submitted
- if (in_array($path, $this->baseNames, true)) {
- return $path;
- }
- $parts = explode('/', Helper::normalizePath($path));
- while (count($parts)) {
- $currentPath = implode('/', $parts);
- if (isset($this->baseNames[$currentPath])) {
- return $currentPath;
- }
- array_pop($parts);
- }
- return null;
- }
- /**
- * @param string $baseName
- */
- private function addBaseName($baseName)
- {
- if (isset($this->baseNames[$baseName])) {
- return;
- }
- $this->baseNames[$baseName] = Helper::normalizeWindowsPath(
- realpath($baseName)
- );
- }
- /**
- * Finds confirmed(!) invocations by alias.
- *
- * @param string $path
- * @return null|PharInvocation
- * @see \TYPO3\PharStreamWrapper\PharStreamWrapper::collectInvocation()
- */
- private function findByAlias($path)
- {
- $possibleAlias = $this->resolvePossibleAlias($path);
- if ($possibleAlias === null) {
- return null;
- }
- return Manager::instance()->getCollection()->findByCallback(
- function (PharInvocation $candidate) use ($possibleAlias) {
- return $candidate->isConfirmed() && $candidate->getAlias() === $possibleAlias;
- },
- true
- );
- }
- }
|