Merge pull request #24109 from yashodha/reports_improvements
[civicrm-core.git] / Civi / Core / Security / PharExtensionInterceptor.php
1 <?php
2
3 namespace Civi\Core\Security;
4
5 use TYPO3\PharStreamWrapper\Assertable;
6 use TYPO3\PharStreamWrapper\Helper;
7 use TYPO3\PharStreamWrapper\Exception;
8
9 /**
10 * An alternate PharExtensionInterceptor to support phar-based CLI tools.
11 *
12 * This is largely based on Drupal\Core\Security\PharExtensionInterceptor,
13 * originally licensed under GPL2+
14 *
15 * @see \TYPO3\PharStreamWrapper\Interceptor\PharExtensionInterceptor
16 */
17 class PharExtensionInterceptor implements Assertable {
18
19 /**
20 * Determines whether phar file is allowed to execute.
21 *
22 * The phar file is allowed to execute if:
23 * - the base file name has a ".phar" suffix.
24 * - it is the CLI tool that has invoked the interceptor.
25 *
26 * @param string $path
27 * The path of the phar file to check.
28 * @param string $command
29 * The command being carried out.
30 *
31 * @return bool
32 * TRUE if the phar file is allowed to execute.
33 *
34 * @throws Exception
35 * Thrown when the file is not allowed to execute.
36 */
37 public function assert(string $path, string $command): bool {
38 if ($this->baseFileContainsPharExtension($path)) {
39 return TRUE;
40 }
41 throw new Exception(
42 sprintf(
43 'Unexpected file extension in "%s"',
44 $path
45 ),
46 1535198703
47 );
48 }
49
50 /**
51 * Determines if a path has a .phar extension or invoked execution.
52 *
53 * @param string $path
54 * The path of the phar file to check.
55 *
56 * @return bool
57 * TRUE if the file has a .phar extension or if the execution has been
58 * invoked by the phar file.
59 */
60 private function baseFileContainsPharExtension($path) {
61 $baseFile = Helper::determineBaseFile($path);
62 if ($baseFile === NULL) {
63 return FALSE;
64 }
65 // If the stream wrapper is registered by invoking a phar file that does
66 // not not have .phar extension then this should be allowed. For
67 // example, some CLI tools recommend removing the extension.
68 $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
69 // Find the last entry in the backtrace containing a 'file' key as
70 // sometimes the last caller is executed outside the scope of a file. For
71 // example, this occurs with shutdown functions.
72 do {
73 $caller = array_pop($backtrace);
74 } while (empty($caller['file']) && !empty($backtrace));
75 if (isset($caller['file']) && $baseFile === Helper::determineBaseFile($caller['file'])) {
76 return TRUE;
77 }
78 $fileExtension = pathinfo($baseFile, PATHINFO_EXTENSION);
79 return strtolower($fileExtension) === 'phar';
80 }
81
82 }