[TASK] Use typo3/phar-stream-wrapper package 78/58778/3
authorOliver Hader <oliver@typo3.org>
Mon, 29 Oct 2018 12:02:39 +0000 (13:02 +0100)
committerOliver Hader <oliver.hader@typo3.org>
Tue, 30 Oct 2018 08:06:34 +0000 (09:06 +0100)
PharStreamWrapper has been released as standalone package under
the MIT license: https://github.com/TYPO3/phar-stream-wrapper

Stream invocation is handled by the new composer package, previous
classes PharStreamWrapper and PharStreamWrapperException have been
removed from the TYPO3 core but are still kept in class alias maps
for compatibility reasons. Since the standalone package is now
independent from TYPO3 constraints, the TYPO3 specific logic to
intercept Phar invocations has been moved to the new class
PharStreamWrapperInterceptor.

`composer require typo3/phar-stream-wrapper:^2.0.1`

Related: #85984
Resolves: #86666
Releases: 8.7, 7.6
Change-Id: I724c4238d1a8184a8c7c908f16d71c06f87244d8
Reviewed-on: https://review.typo3.org/58778
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Oliver Hader <oliver.hader@typo3.org>
Tested-by: Oliver Hader <oliver.hader@typo3.org>
composer.json
composer.lock
typo3/sysext/core/Classes/Core/Bootstrap.php
typo3/sysext/core/Classes/IO/PharStreamWrapper.php [deleted file]
typo3/sysext/core/Classes/IO/PharStreamWrapperException.php [deleted file]
typo3/sysext/core/Classes/IO/PharStreamWrapperInterceptor.php [new file with mode: 0644]
typo3/sysext/core/Classes/Migrations/Code/ClassAliasMap.php [new file with mode: 0644]
typo3/sysext/core/Tests/Functional/IO/PharStreamWrapperInterceptorTest.php [new file with mode: 0644]
typo3/sysext/core/Tests/Functional/IO/PharStreamWrapperTest.php [deleted file]

index 2ad004c..4c6984c 100644 (file)
@@ -47,7 +47,8 @@
                "psr/http-message": "~1.0",
                "cogpowered/finediff": "~0.3.1",
                "mso/idna-convert": "^0.9.1",
-               "symfony/debug": "^2.7"
+               "symfony/debug": "^2.7",
+               "typo3/phar-stream-wrapper": "^2.0.1"
        },
        "require-dev": {
                "phpunit/phpunit": "~4.8.0",
index 2aeab3e..112dbcb 100644 (file)
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "f4481dc0874ad7bc946752be043dd2b7",
+    "content-hash": "7716b519ec42272bb0962b8f8dcb1860",
     "packages": [
         {
             "name": "cogpowered/finediff",
                 "typo3"
             ],
             "time": "2017-12-04T15:30:33+00:00"
+        },
+        {
+            "name": "typo3/phar-stream-wrapper",
+            "version": "v2.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/TYPO3/phar-stream-wrapper.git",
+                "reference": "0469d9fefa0146ea4299d3b11cfbb76faa7045bf"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/TYPO3/phar-stream-wrapper/zipball/0469d9fefa0146ea4299d3b11cfbb76faa7045bf",
+                "reference": "0469d9fefa0146ea4299d3b11cfbb76faa7045bf",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.3.3|^7.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.8.36"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "TYPO3\\PharStreamWrapper\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "Interceptors for PHP's native phar:// stream handling",
+            "homepage": "https://typo3.org/",
+            "keywords": [
+                "phar",
+                "php",
+                "security",
+                "stream-wrapper"
+            ],
+            "time": "2018-10-18T08:46:28+00:00"
         }
     ],
     "packages-dev": [
index a095b7c..d2f73aa 100644 (file)
@@ -14,10 +14,13 @@ namespace TYPO3\CMS\Core\Core;
  * The TYPO3 project - inspiring people to share!
  */
 
-use TYPO3\CMS\Core\IO\PharStreamWrapper;
+use TYPO3\CMS\Core\IO\PharStreamWrapperInterceptor;
 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
+use TYPO3\PharStreamWrapper\Behavior;
+use TYPO3\PharStreamWrapper\Manager;
+use TYPO3\PharStreamWrapper\PharStreamWrapper;
 
 /**
  * This class encapsulates bootstrap related methods.
@@ -726,6 +729,13 @@ class Bootstrap
     protected function initializeIO()
     {
         if (in_array('phar', stream_get_wrappers())) {
+            // destroy and re-initialize PharStreamWrapper for TYPO3 core
+            Manager::destroy();
+            Manager::initialize(
+                (new Behavior())
+                    ->withAssertion(new PharStreamWrapperInterceptor())
+            );
+
             stream_wrapper_unregister('phar');
             stream_wrapper_register('phar', PharStreamWrapper::class);
         }
diff --git a/typo3/sysext/core/Classes/IO/PharStreamWrapper.php b/typo3/sysext/core/Classes/IO/PharStreamWrapper.php
deleted file mode 100644 (file)
index 759061c..0000000
+++ /dev/null
@@ -1,536 +0,0 @@
-<?php
-namespace TYPO3\CMS\Core\IO;
-
-/*
- * This file is part of the TYPO3 CMS project.
- *
- * It is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License, either version 2
- * of the License, or any later version.
- *
- * For the full copyright and license information, please read the
- * LICENSE.txt file that was distributed with this source code.
- *
- * The TYPO3 project - inspiring people to share!
- */
-
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Core\Utility\PathUtility;
-
-class PharStreamWrapper
-{
-    /**
-     * Internal stream constants that are not exposed to PHP, but used...
-     * @see https://github.com/php/php-src/blob/e17fc0d73c611ad0207cac8a4a01ded38251a7dc/main/php_streams.h
-     */
-    const STREAM_OPEN_FOR_INCLUDE = 128;
-
-    /**
-     * @var resource
-     */
-    public $context;
-
-    /**
-     * @var resource
-     */
-    protected $internalResource;
-
-    /**
-     * @return bool
-     */
-    public function dir_closedir()
-    {
-        if (!is_resource($this->internalResource)) {
-            return false;
-        }
-
-        $this->invokeInternalStreamWrapper(
-            'closedir',
-            [ $this->internalResource ]
-        );
-        return !is_resource($this->internalResource);
-    }
-
-    /**
-     * @param string $path
-     * @param int $options
-     * @return bool
-     */
-    public function dir_opendir($path, $options)
-    {
-        $this->assertPath($path);
-        $this->internalResource = $this->invokeInternalStreamWrapper(
-            'opendir',
-            [ $path, $this->context ]
-        );
-        return is_resource($this->internalResource);
-    }
-
-    /**
-     * @return string|false
-     */
-    public function dir_readdir()
-    {
-        return $this->invokeInternalStreamWrapper(
-            'readdir',
-            [ $this->internalResource ]
-        );
-    }
-
-    /**
-     * @return bool
-     */
-    public function dir_rewinddir()
-    {
-        if (!is_resource($this->internalResource)) {
-            return false;
-        }
-
-        $this->invokeInternalStreamWrapper(
-            'rewinddir',
-            [ $this->internalResource ]
-        );
-        return is_resource($this->internalResource);
-    }
-
-    /**
-     * @param string $path
-     * @param int $mode
-     * @param int $options
-     * @return bool
-     */
-    public function mkdir($path, $mode, $options)
-    {
-        $this->assertPath($path);
-        return $this->invokeInternalStreamWrapper(
-            'mkdir',
-            [ $path, $mode, (bool)($options & STREAM_MKDIR_RECURSIVE), $this->context ]
-        );
-    }
-
-    /**
-     * @param string $path_from
-     * @param string $path_to
-     * @return bool
-     */
-    public function rename($path_from, $path_to)
-    {
-        $this->assertPath($path_from);
-        $this->assertPath($path_to);
-        return $this->invokeInternalStreamWrapper(
-            'rename',
-            [ $path_from, $path_to, $this->context ]
-        );
-    }
-
-    public function rmdir($path, $options)
-    {
-        $this->assertPath($path);
-        return $this->invokeInternalStreamWrapper(
-            'rmdir',
-            [ $path, $this->context ]
-        );
-    }
-
-    /**
-     * @param int $cast_as
-     */
-    public function stream_cast($cast_as)
-    {
-        throw new PharStreamWrapperException(
-            'Method stream_select() cannot be used',
-            1530103999
-        );
-    }
-
-    public function stream_close()
-    {
-        $this->invokeInternalStreamWrapper(
-            'fclose',
-            [ $this->internalResource ]
-        );
-    }
-
-    /**
-     * @return bool
-     */
-    public function stream_eof()
-    {
-        return $this->invokeInternalStreamWrapper(
-            'feof',
-            [ $this->internalResource ]
-        );
-    }
-
-    /**
-     * @return bool
-     */
-    public function stream_flush()
-    {
-        return $this->invokeInternalStreamWrapper(
-            'fflush',
-            [ $this->internalResource ]
-        );
-    }
-
-    /**
-     * @param int $operation
-     * @return bool
-     */
-    public function stream_lock($operation)
-    {
-        return $this->invokeInternalStreamWrapper(
-            'flock',
-            [ $this->internalResource, $operation ]
-        );
-    }
-
-    /**
-     * @param string $path
-     * @param int $option
-     * @param string|int $value
-     * @return bool
-     */
-    public function stream_metadata($path, $option, $value)
-    {
-        $this->assertPath($path);
-        if ($option === STREAM_META_TOUCH) {
-            return $this->invokeInternalStreamWrapper(
-                'touch',
-                [ $path, $value ]
-            );
-        }
-        if ($option === STREAM_META_OWNER_NAME || $option === STREAM_META_OWNER) {
-            return $this->invokeInternalStreamWrapper(
-                'chown',
-                [ $path, $value ]
-            );
-        }
-        if ($option === STREAM_META_GROUP_NAME || $option === STREAM_META_GROUP) {
-            return $this->invokeInternalStreamWrapper(
-                'chgrp',
-                [ $path, $value]
-            );
-        }
-        if ($option === STREAM_META_ACCESS) {
-            return $this->invokeInternalStreamWrapper(
-                'chmod',
-                [ $path, $value ]
-            );
-        }
-        return false;
-    }
-
-    /**
-     * @param string $path
-     * @param string $mode
-     * @param int $options
-     * @param string|null $opened_path
-     * @return bool
-     */
-    public function stream_open(
-        $path,
-        $mode,
-        $options,
-        &$opened_path = null
-    )
-    {
-        $this->assertPath($path);
-        $arguments = [$path, $mode, (bool)($options & STREAM_USE_PATH)];
-        // only add stream context for non include/require calls
-        if (!($options & static::STREAM_OPEN_FOR_INCLUDE)) {
-            $arguments[] = $this->context;
-        // work around https://bugs.php.net/bug.php?id=66569
-        // for including files from Phar stream with OPcache enabled
-        } else {
-            $this->resetOpCache();
-        }
-        $this->internalResource = $this->invokeInternalStreamWrapper(
-            'fopen',
-            $arguments
-        );
-        if (!is_resource($this->internalResource)) {
-            return false;
-        }
-        if ($opened_path !== null) {
-            $metaData = stream_get_meta_data($this->internalResource);
-            $opened_path = $metaData['uri'];
-        }
-        return true;
-    }
-
-    /**
-     * @param int $count
-     * @return string
-     */
-    public function stream_read($count)
-    {
-        return $this->invokeInternalStreamWrapper(
-            'fread',
-            [ $this->internalResource, $count ]
-        );
-    }
-
-    /**
-     * @param int $offset
-     * @param int $whence
-     * @return bool
-     */
-    public function stream_seek($offset, $whence = SEEK_SET)
-    {
-        return $this->invokeInternalStreamWrapper(
-            'fseek',
-            [ $this->internalResource, $offset, $whence ]
-        ) !== -1;
-    }
-
-    /**
-     * @param int $option
-     * @param int $arg1
-     * @param int $arg2
-     * @return bool
-     */
-    public function stream_set_option($option, $arg1, $arg2)
-    {
-        if ($option === STREAM_OPTION_BLOCKING) {
-            return $this->invokeInternalStreamWrapper(
-                'stream_set_blocking',
-                [ $this->internalResource, $arg1 ]
-            );
-        }
-        if ($option === STREAM_OPTION_READ_TIMEOUT) {
-            return $this->invokeInternalStreamWrapper(
-                'stream_set_timeout',
-                [ $this->internalResource, $arg1, $arg2 ]
-            );
-        }
-        if ($option === STREAM_OPTION_WRITE_BUFFER) {
-            return $this->invokeInternalStreamWrapper(
-                'stream_set_write_buffer',
-                [ $this->internalResource, $arg2 ]
-            ) === 0;
-        }
-        return false;
-    }
-
-    /**
-     * @return array
-     */
-    public function stream_stat()
-    {
-        return $this->invokeInternalStreamWrapper(
-            'fstat',
-            [ $this->internalResource ]
-        );
-    }
-
-    /**
-     * @return int
-     */
-    public function stream_tell()
-    {
-        return $this->invokeInternalStreamWrapper(
-            'ftell',
-            [ $this->internalResource ]
-        );
-    }
-
-    /**
-     * @param int $new_size
-     * @return bool
-     */
-    public function stream_truncate($new_size)
-    {
-        return $this->invokeInternalStreamWrapper(
-            'ftruncate',
-            [ $this->internalResource, $new_size ]
-        );
-    }
-
-    /**
-     * @param string $data
-     * @return int
-     */
-    public function stream_write($data)
-    {
-        return $this->invokeInternalStreamWrapper(
-            'fwrite',
-            [ $this->internalResource, $data ]
-        );
-    }
-
-    /**
-     * @param string $path
-     * @return bool
-     */
-    public function unlink($path)
-    {
-        $this->assertPath($path);
-        return $this->invokeInternalStreamWrapper(
-            'unlink',
-            [ $path, $this->context ]
-        );
-    }
-
-    /**
-     * @param string $path
-     * @param int $flags
-     * @return array|false
-     */
-    public function url_stat($path, $flags)
-    {
-        $this->assertPath($path);
-        $functionName = $flags & STREAM_URL_STAT_QUIET ? '@stat' : 'stat';
-        return $this->invokeInternalStreamWrapper(
-            $functionName,
-            [ $path ]
-        );
-    }
-
-    /**
-     * @param string $path
-     * @return bool
-     */
-    protected function isAllowed($path)
-    {
-        $path = $this->determineBaseFile($path);
-        if (!GeneralUtility::isAbsPath($path)) {
-            $path = PATH_site . $path;
-        }
-
-        if (GeneralUtility::validPathStr($path)
-            && GeneralUtility::isFirstPartOfStr(
-                $path,
-                PATH_site . 'typo3conf/ext/'
-            )
-        ) {
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Normalizes a path, removes phar:// prefix, fixes Windows directory
-     * separators. Result is without trailing slash.
-     *
-     * @param string $path
-     * @return string
-     */
-    protected function normalizePath($path)
-    {
-        return rtrim(
-            PathUtility::getCanonicalPath(
-                GeneralUtility::fixWindowsFilePath(
-                    $this->removePharPrefix($path)
-                )
-            ),
-            '/'
-        );
-    }
-
-    /**
-     * @param string $path
-     * @return string
-     */
-    protected function removePharPrefix($path)
-    {
-        return preg_replace('#^phar://#i', '', $path);
-    }
-
-    /**
-     * Determines base file that can be accessed using the regular file system.
-     * For e.g. "phar:///home/user/bundle.phar/content.txt" that would result
-     * into "/home/user/bundle.phar".
-     *
-     * @param string $path
-     * @return string|null
-     */
-    protected function determineBaseFile($path)
-    {
-        $parts = explode('/', $this->normalizePath($path));
-
-        while (count($parts)) {
-            $currentPath = implode('/', $parts);
-            if (@file_exists($currentPath)) {
-                return $currentPath;
-            }
-            array_pop($parts);
-        }
-
-        return null;
-    }
-
-    /**
-     * Determines whether the requested path is the base file.
-     *
-     * @param string $path
-     * @return bool
-     * @deprecated Currently not used
-     */
-    protected function isBaseFile($path)
-    {
-        $path = $this->normalizePath($path);
-        $baseFile = $this->determineBaseFile($path);
-        return $path === $baseFile;
-    }
-
-    /**
-     * Asserts the given path to a Phar file.
-     *
-     * @param string $path
-     * @throws PharStreamWrapperException
-     */
-    protected function assertPath($path)
-    {
-        if (!$this->isAllowed($path)) {
-            throw new PharStreamWrapperException(
-                sprintf('Executing  %s is denied', $path),
-                1530103998
-            );
-        }
-    }
-
-    protected function resetOpCache()
-    {
-        if (function_exists('opcache_reset')
-            && function_exists('opcache_get_status')
-            && !empty(opcache_get_status()['opcache_enabled'])
-        ) {
-            opcache_reset();
-        }
-    }
-
-    /**
-     * Invokes commands on the native PHP Phar stream wrapper.
-     *
-     * @param string $functionName
-     * @param array $arguments
-     * @return mixed
-     */
-    protected function invokeInternalStreamWrapper($functionName, array $arguments)
-    {
-        $silentExecution = $functionName{0} === '@';
-        $functionName = ltrim($functionName, '@');
-        $this->restoreInternalSteamWrapper();
-
-        if ($silentExecution) {
-            $result = @call_user_func_array($functionName, $arguments);
-        } else {
-            $result = call_user_func_array($functionName, $arguments);
-        }
-
-        $this->registerStreamWrapper();
-        return $result;
-    }
-
-    protected function restoreInternalSteamWrapper()
-    {
-        stream_wrapper_restore('phar');
-    }
-
-    protected function registerStreamWrapper()
-    {
-        stream_wrapper_unregister('phar');
-        stream_wrapper_register('phar', static::class);
-    }
-}
diff --git a/typo3/sysext/core/Classes/IO/PharStreamWrapperException.php b/typo3/sysext/core/Classes/IO/PharStreamWrapperException.php
deleted file mode 100644 (file)
index 66a701a..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-<?php
-namespace TYPO3\CMS\Core\IO;
-
-/*
- * This file is part of the TYPO3 CMS project.
- *
- * It is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License, either version 2
- * of the License, or any later version.
- *
- * For the full copyright and license information, please read the
- * LICENSE.txt file that was distributed with this source code.
- *
- * The TYPO3 project - inspiring people to share!
- */
-
-class PharStreamWrapperException extends \RuntimeException
-{
-}
diff --git a/typo3/sysext/core/Classes/IO/PharStreamWrapperInterceptor.php b/typo3/sysext/core/Classes/IO/PharStreamWrapperInterceptor.php
new file mode 100644 (file)
index 0000000..995aae3
--- /dev/null
@@ -0,0 +1,116 @@
+<?php
+namespace TYPO3\CMS\Core\IO;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\PathUtility;
+use TYPO3\PharStreamWrapper\Exception;
+
+class PharStreamWrapperInterceptor implements \TYPO3\PharStreamWrapper\Assertable
+{
+    /**
+     * Asserts the given path of a Phar file is located in a valid path
+     * in typo3conf/ext/* of the local TYPO3 installation.
+     *
+     * @param string $path
+     * @param string $command
+     * @return bool
+     * @throws Exception
+     */
+    public function assert($path, $command)
+    {
+        if ($this->isAllowed($path) === true) {
+            return true;
+        }
+        throw new Exception(
+            sprintf('Executing %s is denied', $path),
+            1530103998
+        );
+    }
+
+    /**
+     * @param string $path
+     * @return bool
+     */
+    protected function isAllowed($path)
+    {
+        $path = $this->determineBaseFile($path);
+        if (!GeneralUtility::isAbsPath($path)) {
+            $path = PATH_site . $path;
+        }
+
+        if (GeneralUtility::validPathStr($path)
+            && GeneralUtility::isFirstPartOfStr(
+                $path,
+                PATH_site . 'typo3conf/ext/'
+            )
+        ) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Normalizes a path, removes phar:// prefix, fixes Windows directory
+     * separators. Result is without trailing slash.
+     *
+     * @param string $path
+     * @return string
+     */
+    protected function normalizePath($path)
+    {
+        return rtrim(
+            PathUtility::getCanonicalPath(
+                GeneralUtility::fixWindowsFilePath(
+                    $this->removePharPrefix($path)
+                )
+            ),
+            '/'
+        );
+    }
+
+    /**
+     * @param string $path
+     * @return string
+     */
+    protected function removePharPrefix($path)
+    {
+        return preg_replace('#^phar://#i', '', $path);
+    }
+
+    /**
+     * Determines base file that can be accessed using the regular file system.
+     * For e.g. "phar:///home/user/bundle.phar/content.txt" that would result
+     * into "/home/user/bundle.phar".
+     *
+     * @param string $path
+     * @return string|null
+     */
+    protected function determineBaseFile($path)
+    {
+        $parts = explode('/', $this->normalizePath($path));
+
+        while (count($parts)) {
+            $currentPath = implode('/', $parts);
+            if (@file_exists($currentPath)) {
+                return $currentPath;
+            }
+            array_pop($parts);
+        }
+
+        return null;
+    }
+}
diff --git a/typo3/sysext/core/Classes/Migrations/Code/ClassAliasMap.php b/typo3/sysext/core/Classes/Migrations/Code/ClassAliasMap.php
new file mode 100644 (file)
index 0000000..81319eb
--- /dev/null
@@ -0,0 +1,5 @@
+<?php
+return [
+    'TYPO3\\CMS\\Core\\IO\\PharStreamWrapper' => \TYPO3\PharStreamWrapper\PharStreamWrapper::class,
+    'TYPO3\\CMS\\Core\\IO\\PharStreamWrapperException' => \TYPO3\PharStreamWrapper\Exception::class,
+];
diff --git a/typo3/sysext/core/Tests/Functional/IO/PharStreamWrapperInterceptorTest.php b/typo3/sysext/core/Tests/Functional/IO/PharStreamWrapperInterceptorTest.php
new file mode 100644 (file)
index 0000000..9f042c3
--- /dev/null
@@ -0,0 +1,389 @@
+<?php
+namespace TYPO3\CMS\Core\Tests\Functional\IO;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Tests\FunctionalTestCase;
+use TYPO3\PharStreamWrapper\Exception;
+
+class PharStreamWrapperInterceptorTest extends FunctionalTestCase
+{
+    /**
+     * @var array
+     */
+    protected $testExtensionsToLoad = [
+        'typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_resources'
+    ];
+
+    /**
+     * @var array
+     */
+    protected $pathsToLinkInTestInstance = [
+        'typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_resources/bundle.phar' => 'fileadmin/bundle.phar'
+    ];
+
+    protected function setUp()
+    {
+        parent::setUp();
+
+        if (!in_array('phar', stream_get_wrappers())) {
+            $this->markTestSkipped('Phar stream wrapper is not registered');
+        }
+        // PharStreamWrapper is not initialized here since it relies on being
+        // properly defined in \TYPO3\CMS\Core\Core\Bootstrap - thus, it tests
+        // are expected to fail in case PharStreamWrapper is not initialized
+    }
+
+    public function directoryActionAllowsInvocationDataProvider()
+    {
+        $allowedPath = 'typo3conf/ext/test_resources/bundle.phar';
+
+        return [
+            'root directory' => [
+                $allowedPath,
+                ['Classes', 'Resources']
+            ],
+            'Classes/Domain/Model directory' => [
+                $allowedPath . '/Classes/Domain/Model',
+                ['DemoModel.php']
+            ],
+            'Resources directory' => [
+                $allowedPath . '/Resources',
+                ['content.txt']
+            ],
+        ];
+    }
+
+    /**
+     * @param string $path
+     *
+     * @test
+     * @dataProvider directoryActionAllowsInvocationDataProvider
+     */
+    public function directoryOpenAllowsInvocation($path)
+    {
+        $path = $this->getInstancePath() . '/' . $path;
+        $handle = opendir('phar://' . $path);
+        self::assertInternalType('resource', $handle);
+    }
+
+    /**
+     * @param string $path
+     * @param $expectation
+     *
+     * @test
+     * @dataProvider directoryActionAllowsInvocationDataProvider
+     */
+    public function directoryReadAllowsInvocation($path, array $expectation)
+    {
+        $path = $this->getInstancePath() . '/' . $path;
+
+        $items = [];
+        $handle = opendir('phar://' . $path);
+        while (false !== $item = readdir($handle)) {
+            $items[] = $item;
+        }
+
+        self::assertSame($expectation, $items);
+    }
+
+    /**
+     * @param string $path
+     * @param $expectation
+     *
+     * @test
+     * @dataProvider directoryActionAllowsInvocationDataProvider
+     */
+    public function directoryCloseAllowsInvocation($path, array $expectation)
+    {
+        $path = $this->getInstancePath() . '/' . $path;
+
+        $handle = opendir('phar://' . $path);
+        closedir($handle);
+
+        self::assertFalse(is_resource($handle));
+    }
+
+    public function directoryActionDeniesInvocationDataProvider()
+    {
+        $deniedPath = 'fileadmin/bundle.phar';
+
+        return [
+            'root directory' => [
+                $deniedPath,
+                ['Classes', 'Resources']
+            ],
+            'Classes/Domain/Model directory' => [
+                $deniedPath . '/Classes/Domain/Model',
+                ['DemoModel.php']
+            ],
+            'Resources directory' => [
+                $deniedPath . '/Resources',
+                ['content.txt']
+            ],
+        ];
+    }
+
+    /**
+     * @param string $path
+     *
+     * @test
+     * @dataProvider directoryActionDeniesInvocationDataProvider
+     * @expectedException Exception
+     * @expectedExceptionCode 1530103998
+     */
+    public function directoryActionDeniesInvocation($path)
+    {
+        $path = $this->getInstancePath() . '/' . $path;
+        opendir('phar://' . $path);
+    }
+
+    /**
+     * @return array
+     */
+    public function urlStatAllowsInvocationDataProvider()
+    {
+        $allowedPath = 'typo3conf/ext/test_resources/bundle.phar';
+
+        return [
+            'filesize base file' => [
+                'filesize',
+                $allowedPath,
+                0, // Phar base file always has zero size when accessed through phar://
+            ],
+            'filesize Resources/content.txt' => [
+                'filesize',
+                $allowedPath . '/Resources/content.txt',
+                21,
+            ],
+            'is_file base file' => [
+                'is_file',
+                $allowedPath,
+                false, // Phar base file is not a file when accessed through phar://
+            ],
+            'is_file Resources/content.txt' => [
+                'is_file',
+                $allowedPath . '/Resources/content.txt',
+                true,
+            ],
+            'is_dir base file' => [
+                'is_dir',
+                $allowedPath,
+                true, // Phar base file is a directory when accessed through phar://
+            ],
+            'is_dir Resources/content.txt' => [
+                'is_dir',
+                $allowedPath . '/Resources/content.txt',
+                false,
+            ],
+            'file_exists base file' => [
+                'file_exists',
+                $allowedPath,
+                true
+            ],
+            'file_exists Resources/content.txt' => [
+                'file_exists',
+                $allowedPath . '/Resources/content.txt',
+                true
+            ],
+        ];
+    }
+
+    /**
+     * @param string $functionName
+     * @param string $path
+     * @param mixed $expectation
+     *
+     * @test
+     * @dataProvider urlStatAllowsInvocationDataProvider
+     */
+    public function urlStatAllowsInvocation($functionName, $path, $expectation)
+    {
+        $path = $this->getInstancePath() . '/' . $path;
+
+        self::assertSame(
+            $expectation,
+            call_user_func($functionName, 'phar://' . $path)
+        );
+    }
+
+    /**
+     * @return array
+     */
+    public function urlStatDeniesInvocationDataProvider()
+    {
+        $deniedPath = 'fileadmin/bundle.phar';
+
+        return [
+            'filesize base file' => [
+                'filesize',
+                $deniedPath,
+                0, // Phar base file always has zero size when accessed through phar://
+            ],
+            'filesize Resources/content.txt' => [
+                'filesize',
+                $deniedPath . '/Resources/content.txt',
+                21,
+            ],
+            'is_file base file' => [
+                'is_file',
+                $deniedPath,
+                false, // Phar base file is not a file when accessed through phar://
+            ],
+            'is_file Resources/content.txt' => [
+                'is_file',
+                $deniedPath . '/Resources/content.txt',
+                true,
+            ],
+            'is_dir base file' => [
+                'is_dir',
+                $deniedPath,
+                true, // Phar base file is a directory when accessed through phar://
+            ],
+            'is_dir Resources/content.txt' => [
+                'is_dir',
+                $deniedPath . '/Resources/content.txt',
+                false,
+            ],
+            'file_exists base file' => [
+                'file_exists',
+                $deniedPath,
+                true
+            ],
+            'file_exists Resources/content.txt' => [
+                'file_exists',
+                $deniedPath . '/Resources/content.txt',
+                true
+            ],
+        ];
+    }
+
+    /**
+     * @param string $functionName
+     * @param string $path
+     * @param mixed $expectation
+     *
+     * @test
+     * @dataProvider urlStatDeniesInvocationDataProvider
+     * @expectedException Exception
+     * @expectedExceptionCode 1530103998
+     */
+    public function urlStatDeniesInvocation($functionName, $path)
+    {
+        $path = $this->getInstancePath() . '/' . $path;
+        call_user_func($functionName, 'phar://' . $path);
+    }
+
+    /**
+     * @test
+     */
+    public function streamOpenAllowsInvocationForFileOpen()
+    {
+        $allowedPath = $this->getInstancePath() . '/typo3conf/ext/test_resources/bundle.phar';
+        $handle = fopen('phar://' . $allowedPath . '/Resources/content.txt', 'r');
+        self::assertInternalType('resource', $handle);
+    }
+
+    /**
+     * @test
+     */
+    public function streamOpenAllowsInvocationForFileRead()
+    {
+        $allowedPath = $this->getInstancePath() . '/typo3conf/ext/test_resources/bundle.phar';
+        $handle = fopen('phar://' . $allowedPath . '/Resources/content.txt', 'r');
+        $content = fread($handle, 1024);
+        self::assertSame('TYPO3 demo text file.', $content);
+    }
+
+    /**
+     * @test
+     */
+    public function streamOpenAllowsInvocationForFileEnd()
+    {
+        $allowedPath = $this->getInstancePath() . '/typo3conf/ext/test_resources/bundle.phar';
+        $handle = fopen('phar://' . $allowedPath . '/Resources/content.txt', 'r');
+        fread($handle, 1024);
+        self::assertTrue(feof($handle));
+    }
+
+    /**
+     * @test
+     */
+    public function streamOpenAllowsInvocationForFileClose()
+    {
+        $allowedPath = $this->getInstancePath() . '/typo3conf/ext/test_resources/bundle.phar';
+        $handle = fopen('phar://' . $allowedPath . '/Resources/content.txt', 'r');
+        fclose($handle);
+        self::assertFalse(is_resource($handle));
+    }
+
+    /**
+     * @test
+     */
+    public function streamOpenAllowsInvocationForFileGetContents()
+    {
+        $allowedPath = $this->getInstancePath() . '/typo3conf/ext/test_resources/bundle.phar';
+        $content = file_get_contents('phar://' . $allowedPath . '/Resources/content.txt');
+        self::assertSame('TYPO3 demo text file.', $content);
+    }
+
+    /**
+     * @test
+     */
+    public function streamOpenAllowsInvocationForInclude()
+    {
+        $allowedPath = $this->getInstancePath() . '/typo3conf/ext/test_resources/bundle.phar';
+        include('phar://' . $allowedPath . '/Classes/Domain/Model/DemoModel.php');
+
+        self::assertTrue(
+            class_exists(
+                \TYPO3Demo\Demo\Domain\Model\DemoModel::class,
+                false
+            )
+        );
+    }
+
+    /**
+     * @test
+     * @expectedException Exception
+     * @expectedExceptionCode 1530103998
+     */
+    public function streamOpenDeniesInvocationForFileOpen()
+    {
+        $allowedPath = $this->getInstancePath() . '/fileadmin/bundle.phar';
+        fopen('phar://' . $allowedPath . '/Resources/content.txt', 'r');
+    }
+
+    /**
+     * @test
+     * @expectedException Exception
+     * @expectedExceptionCode 1530103998
+     */
+    public function streamOpenDeniesInvocationForFileGetContents()
+    {
+        $allowedPath = $this->getInstancePath() . '/fileadmin/bundle.phar';
+        file_get_contents('phar://' . $allowedPath . '/Resources/content.txt');
+    }
+
+    /**
+     * @test
+     * @expectedException Exception
+     * @expectedExceptionCode 1530103998
+     */
+    public function streamOpenDeniesInvocationForInclude()
+    {
+        $allowedPath = $this->getInstancePath() . '/fileadmin/bundle.phar';
+        include('phar://' . $allowedPath . '/Classes/Domain/Model/DemoModel.php');
+    }
+}
diff --git a/typo3/sysext/core/Tests/Functional/IO/PharStreamWrapperTest.php b/typo3/sysext/core/Tests/Functional/IO/PharStreamWrapperTest.php
deleted file mode 100644 (file)
index f034ed5..0000000
+++ /dev/null
@@ -1,395 +0,0 @@
-<?php
-namespace TYPO3\CMS\Core\Tests\Functional\IO;
-
-/*
- * This file is part of the TYPO3 CMS project.
- *
- * It is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License, either version 2
- * of the License, or any later version.
- *
- * For the full copyright and license information, please read the
- * LICENSE.txt file that was distributed with this source code.
- *
- * The TYPO3 project - inspiring people to share!
- */
-
-use TYPO3\CMS\Core\IO\PharStreamWrapper;
-use TYPO3\CMS\Core\Tests\FunctionalTestCase;
-
-class PharStreamWrapperTest extends FunctionalTestCase
-{
-    /**
-     * @var array
-     */
-    protected $testExtensionsToLoad = [
-        'typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_resources'
-    ];
-
-    /**
-     * @var array
-     */
-    protected $pathsToLinkInTestInstance = [
-        'typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_resources/bundle.phar' => 'fileadmin/bundle.phar'
-    ];
-
-    protected function setUp()
-    {
-        parent::setUp();
-
-        if (!in_array('phar', stream_get_wrappers())) {
-            $this->markTestSkipped('Phar stream wrapper is not registered');
-        }
-
-        stream_wrapper_unregister('phar');
-        stream_wrapper_register('phar', PharStreamWrapper::class);
-    }
-
-    protected function tearDown()
-    {
-        stream_wrapper_restore('phar');
-        parent::tearDown();
-    }
-
-    public function directoryActionAllowsInvocationDataProvider()
-    {
-        $allowedPath = 'typo3conf/ext/test_resources/bundle.phar';
-
-        return [
-            'root directory' => [
-                $allowedPath,
-                ['Classes', 'Resources']
-            ],
-            'Classes/Domain/Model directory' => [
-                $allowedPath . '/Classes/Domain/Model',
-                ['DemoModel.php']
-            ],
-            'Resources directory' => [
-                $allowedPath . '/Resources',
-                ['content.txt']
-            ],
-        ];
-    }
-
-    /**
-     * @param string $path
-     *
-     * @test
-     * @dataProvider directoryActionAllowsInvocationDataProvider
-     */
-    public function directoryOpenAllowsInvocation($path)
-    {
-        $path = $this->getInstancePath() . '/' . $path;
-        $handle = opendir('phar://' . $path);
-        self::assertInternalType('resource', $handle);
-    }
-
-    /**
-     * @param string $path
-     * @param $expectation
-     *
-     * @test
-     * @dataProvider directoryActionAllowsInvocationDataProvider
-     */
-    public function directoryReadAllowsInvocation($path, array $expectation)
-    {
-        $path = $this->getInstancePath() . '/' . $path;
-
-        $items = [];
-        $handle = opendir('phar://' . $path);
-        while (false !== $item = readdir($handle)) {
-            $items[] = $item;
-        }
-
-        self::assertSame($expectation, $items);
-    }
-
-    /**
-     * @param string $path
-     * @param $expectation
-     *
-     * @test
-     * @dataProvider directoryActionAllowsInvocationDataProvider
-     */
-    public function directoryCloseAllowsInvocation($path, array $expectation)
-    {
-        $path = $this->getInstancePath() . '/' . $path;
-
-        $handle = opendir('phar://' . $path);
-        closedir($handle);
-
-        self::assertFalse(is_resource($handle));
-    }
-
-    public function directoryActionDeniesInvocationDataProvider()
-    {
-        $deniedPath = 'fileadmin/bundle.phar';
-
-        return [
-            'root directory' => [
-                $deniedPath,
-                ['Classes', 'Resources']
-            ],
-            'Classes/Domain/Model directory' => [
-                $deniedPath . '/Classes/Domain/Model',
-                ['DemoModel.php']
-            ],
-            'Resources directory' => [
-                $deniedPath . '/Resources',
-                ['content.txt']
-            ],
-        ];
-    }
-
-    /**
-     * @param string $path
-     *
-     * @test
-     * @dataProvider directoryActionDeniesInvocationDataProvider
-     * @expectedException \TYPO3\CMS\Core\IO\PharStreamWrapperException
-     * @expectedExceptionCode 1530103998
-     */
-    public function directoryActionDeniesInvocation($path)
-    {
-        $path = $this->getInstancePath() . '/' . $path;
-        opendir('phar://' . $path);
-    }
-
-    /**
-     * @return array
-     */
-    public function urlStatAllowsInvocationDataProvider()
-    {
-        $allowedPath = 'typo3conf/ext/test_resources/bundle.phar';
-
-        return [
-            'filesize base file' => [
-                'filesize',
-                $allowedPath,
-                0, // Phar base file always has zero size when accessed through phar://
-            ],
-            'filesize Resources/content.txt' => [
-                'filesize',
-                $allowedPath . '/Resources/content.txt',
-                21,
-            ],
-            'is_file base file' => [
-                'is_file',
-                $allowedPath,
-                false, // Phar base file is not a file when accessed through phar://
-            ],
-            'is_file Resources/content.txt' => [
-                'is_file',
-                $allowedPath . '/Resources/content.txt',
-                true,
-            ],
-            'is_dir base file' => [
-                'is_dir',
-                $allowedPath,
-                true, // Phar base file is a directory when accessed through phar://
-            ],
-            'is_dir Resources/content.txt' => [
-                'is_dir',
-                $allowedPath . '/Resources/content.txt',
-                false,
-            ],
-            'file_exists base file' => [
-                'file_exists',
-                $allowedPath,
-                true
-            ],
-            'file_exists Resources/content.txt' => [
-                'file_exists',
-                $allowedPath . '/Resources/content.txt',
-                true
-            ],
-        ];
-    }
-
-    /**
-     * @param string $functionName
-     * @param string $path
-     * @param mixed $expectation
-     *
-     * @test
-     * @dataProvider urlStatAllowsInvocationDataProvider
-     */
-    public function urlStatAllowsInvocation($functionName, $path, $expectation)
-    {
-        $path = $this->getInstancePath() . '/' . $path;
-
-        self::assertSame(
-            $expectation,
-            call_user_func($functionName, 'phar://' . $path)
-        );
-    }
-
-    /**
-     * @return array
-     */
-    public function urlStatDeniesInvocationDataProvider()
-    {
-        $deniedPath = 'fileadmin/bundle.phar';
-
-        return [
-            'filesize base file' => [
-                'filesize',
-                $deniedPath,
-                0, // Phar base file always has zero size when accessed through phar://
-            ],
-            'filesize Resources/content.txt' => [
-                'filesize',
-                $deniedPath . '/Resources/content.txt',
-                21,
-            ],
-            'is_file base file' => [
-                'is_file',
-                $deniedPath,
-                false, // Phar base file is not a file when accessed through phar://
-            ],
-            'is_file Resources/content.txt' => [
-                'is_file',
-                $deniedPath . '/Resources/content.txt',
-                true,
-            ],
-            'is_dir base file' => [
-                'is_dir',
-                $deniedPath,
-                true, // Phar base file is a directory when accessed through phar://
-            ],
-            'is_dir Resources/content.txt' => [
-                'is_dir',
-                $deniedPath . '/Resources/content.txt',
-                false,
-            ],
-            'file_exists base file' => [
-                'file_exists',
-                $deniedPath,
-                true
-            ],
-            'file_exists Resources/content.txt' => [
-                'file_exists',
-                $deniedPath . '/Resources/content.txt',
-                true
-            ],
-        ];
-    }
-
-    /**
-     * @param string $functionName
-     * @param string $path
-     * @param mixed $expectation
-     *
-     * @test
-     * @dataProvider urlStatDeniesInvocationDataProvider
-     * @expectedException \TYPO3\CMS\Core\IO\PharStreamWrapperException
-     * @expectedExceptionCode 1530103998
-     */
-    public function urlStatDeniesInvocation($functionName, $path)
-    {
-        $path = $this->getInstancePath() . '/' . $path;
-        call_user_func($functionName, 'phar://' . $path);
-    }
-
-    /**
-     * @test
-     */
-    public function streamOpenAllowsInvocationForFileOpen()
-    {
-        $allowedPath = $this->getInstancePath() . '/typo3conf/ext/test_resources/bundle.phar';
-        $handle = fopen('phar://' . $allowedPath . '/Resources/content.txt', 'r');
-        self::assertInternalType('resource', $handle);
-    }
-
-    /**
-     * @test
-     */
-    public function streamOpenAllowsInvocationForFileRead()
-    {
-        $allowedPath = $this->getInstancePath() . '/typo3conf/ext/test_resources/bundle.phar';
-        $handle = fopen('phar://' . $allowedPath . '/Resources/content.txt', 'r');
-        $content = fread($handle, 1024);
-        self::assertSame('TYPO3 demo text file.', $content);
-    }
-
-    /**
-     * @test
-     */
-    public function streamOpenAllowsInvocationForFileEnd()
-    {
-        $allowedPath = $this->getInstancePath() . '/typo3conf/ext/test_resources/bundle.phar';
-        $handle = fopen('phar://' . $allowedPath . '/Resources/content.txt', 'r');
-        fread($handle, 1024);
-        self::assertTrue(feof($handle));
-    }
-
-    /**
-     * @test
-     */
-    public function streamOpenAllowsInvocationForFileClose()
-    {
-        $allowedPath = $this->getInstancePath() . '/typo3conf/ext/test_resources/bundle.phar';
-        $handle = fopen('phar://' . $allowedPath . '/Resources/content.txt', 'r');
-        fclose($handle);
-        self::assertFalse(is_resource($handle));
-    }
-
-    /**
-     * @test
-     */
-    public function streamOpenAllowsInvocationForFileGetContents()
-    {
-        $allowedPath = $this->getInstancePath() . '/typo3conf/ext/test_resources/bundle.phar';
-        $content = file_get_contents('phar://' . $allowedPath . '/Resources/content.txt');
-        self::assertSame('TYPO3 demo text file.', $content);
-    }
-
-    /**
-     * @test
-     */
-    public function streamOpenAllowsInvocationForInclude()
-    {
-        $allowedPath = $this->getInstancePath() . '/typo3conf/ext/test_resources/bundle.phar';
-        include('phar://' . $allowedPath . '/Classes/Domain/Model/DemoModel.php');
-
-        self::assertTrue(
-            class_exists(
-                \TYPO3Demo\Demo\Domain\Model\DemoModel::class,
-                false
-            )
-        );
-    }
-
-    /**
-     * @test
-     * @expectedException \TYPO3\CMS\Core\IO\PharStreamWrapperException
-     * @expectedExceptionCode 1530103998
-     */
-    public function streamOpenDeniesInvocationForFileOpen()
-    {
-        $allowedPath = $this->getInstancePath() . '/fileadmin/bundle.phar';
-        fopen('phar://' . $allowedPath . '/Resources/content.txt', 'r');
-    }
-
-    /**
-     * @test
-     * @expectedException \TYPO3\CMS\Core\IO\PharStreamWrapperException
-     * @expectedExceptionCode 1530103998
-     */
-    public function streamOpenDeniesInvocationForFileGetContents()
-    {
-        $allowedPath = $this->getInstancePath() . '/fileadmin/bundle.phar';
-        file_get_contents('phar://' . $allowedPath . '/Resources/content.txt');
-    }
-
-    /**
-     * @test
-     * @expectedException \TYPO3\CMS\Core\IO\PharStreamWrapperException
-     * @expectedExceptionCode 1530103998
-     */
-    public function streamOpenDeniesInvocationForInclude()
-    {
-        $allowedPath = $this->getInstancePath() . '/fileadmin/bundle.phar';
-        include('phar://' . $allowedPath . '/Classes/Domain/Model/DemoModel.php');
-    }
-}