Seditio Source
Root |
./othercms/drupal-7.90/misc/typo3/phar-stream-wrapper/src/Resolver/PharInvocationResolver.php
<?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
       
);
    }
}