‪TYPO3CMS  ‪main
CommandUtility.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
21 
54 {
58  protected static bool ‪$initialized = false;
59 
69  protected static array ‪$applications = [];
70 
78  protected static ?array ‪$paths = null;
79 
85  public static function ‪exec(string $command, ?array &‪$output = null, int &$returnValue = 0): string
86  {
87  return ‪exec($command, ‪$output, $returnValue);
88  }
89 
98  public static function ‪imageMagickCommand(string $command, string $parameters, string $path = ''): string
99  {
100  $gfxConf = ‪$GLOBALS['TYPO3_CONF_VARS']['GFX'];
101  $isExt = ‪Environment::isWindows() ? '.exe' : '';
102  if (!$path) {
103  $path = $gfxConf['processor_path'];
104  }
105  $path = GeneralUtility::fixWindowsFilePath($path);
106  // This is only used internally, has no effect outside
107  if ($command === 'combine') {
108  $command = 'composite';
109  }
110  // Compile the path & command
111  if ($gfxConf['processor'] === 'GraphicsMagick') {
112  $path = self::escapeShellArgument($path . 'gm' . $isExt) . ' ' . self::escapeShellArgument($command);
113  } else {
114  if (‪Environment::isWindows() && !@is_file($path . $command . $isExt)) {
115  $path = self::escapeShellArgument($path . 'magick' . $isExt) . ' ' . self::escapeShellArgument($command);
116  } else {
117  $path = self::escapeShellArgument($path . $command . $isExt);
118  }
119  }
120  // strip profile information for thumbnails and reduce their size
121  if ($parameters && $command !== 'identify') {
122  // Determine whether the strip profile action has be disabled by TypoScript:
123  if ($gfxConf['processor_stripColorProfileByDefault']
124  && $gfxConf['processor_stripColorProfileCommand'] !== ''
125  && $parameters !== '-version'
126  && !str_contains($parameters, $gfxConf['processor_stripColorProfileCommand'])
127  && !str_contains($parameters, '###SkipStripProfile###')
128  ) {
129  $parameters = $gfxConf['processor_stripColorProfileCommand'] . ' ' . $parameters;
130  } else {
131  $parameters = str_replace('###SkipStripProfile###', '', $parameters);
132  }
133  }
134  // Add -auto-orient on convert so IM/GM respects the image orient
135  if ($parameters && $command === 'convert') {
136  $parameters = '-auto-orient ' . $parameters;
137  }
138  // set interlace parameter for convert command
139  if ($command !== 'identify' && $gfxConf['processor_interlace']) {
140  $parameters = '-interlace ' . $gfxConf['processor_interlace'] . ' ' . $parameters;
141  }
142  $cmdLine = $path . ' ' . $parameters;
143  // It is needed to change the parameters order when a mask image has been specified
144  if ($command === 'composite') {
145  $paramsArr = self::unQuoteFilenames($parameters);
146  $paramsArrCount = count($paramsArr);
147  if ($paramsArrCount > 5) {
148  $tmp = $paramsArr[$paramsArrCount - 3];
149  $paramsArr[$paramsArrCount - 3] = $paramsArr[$paramsArrCount - 4];
150  $paramsArr[$paramsArrCount - 4] = $tmp;
151  }
152  $cmdLine = $path . ' ' . implode(' ', $paramsArr);
153  }
154  return $cmdLine;
155  }
156 
164  public static function ‪checkCommand(string $cmd, string $handler = ''): bool|int
165  {
166  if (!self::init()) {
167  return false;
168  }
169 
170  if ($handler !== '' && !self::checkCommand($handler)) {
171  return -1;
172  }
173  // Already checked and valid
174  if (self::$applications[$cmd]['valid'] ?? false) {
175  return true;
176  }
177  // Is set but was (above) not TRUE
178  if (isset(self::$applications[$cmd]['valid'])) {
179  return false;
180  }
181 
182  foreach (self::$paths as $path => $validPath) {
183  // Ignore invalid (FALSE) paths
184  if ($validPath) {
186  // Windows OS
187  // @todo Why is_executable() is not called here?
188  if (@is_file($path . $cmd)) {
189  self::$applications[$cmd]['app'] = $cmd;
190  self::$applications[$cmd]['path'] = $path;
191  self::$applications[$cmd]['valid'] = true;
192  return true;
193  }
194  if (@is_file($path . $cmd . '.exe')) {
195  self::$applications[$cmd]['app'] = $cmd . '.exe';
196  self::$applications[$cmd]['path'] = $path;
197  self::$applications[$cmd]['valid'] = true;
198  return true;
199  }
200  } else {
201  // Unix-like OS
202  $filePath = realpath($path . $cmd);
203  if ($filePath && @is_executable($filePath)) {
204  self::$applications[$cmd]['app'] = $cmd;
205  self::$applications[$cmd]['path'] = $path;
206  self::$applications[$cmd]['valid'] = true;
207  return true;
208  }
209  }
210  }
211  }
212 
213  // Try to get the executable with the command 'which'.
214  // It does the same like already done, but maybe on other paths
215  if (!‪Environment::isWindows()) {
216  $cmd = @‪self::exec('which ' . self::escapeShellArgument($cmd));
217  if (@is_executable($cmd)) {
218  self::$applications[$cmd]['app'] = $cmd;
219  self::$applications[$cmd]['path'] = ‪PathUtility::dirname($cmd) . '/';
220  self::$applications[$cmd]['valid'] = true;
221  return true;
222  }
223  }
224 
225  return false;
226  }
227 
236  public static function ‪getCommand(string $cmd, string $handler = '', string $handlerOpt = ''): string|bool|int
237  {
238  if (!self::init()) {
239  return false;
240  }
241 
242  // Handler
243  if ($handler) {
244  $handler = ‪self::getCommand($handler);
245 
246  if (!$handler) {
247  return -1;
248  }
249  $handler .= ' ' . escapeshellcmd($handlerOpt) . ' ';
250  }
251 
252  // Command
253  if (!self::checkCommand($cmd)) {
254  return false;
255  }
256  $cmd = self::$applications[$cmd]['path'] . self::$applications[$cmd]['app'] . ' ';
257 
258  return trim($handler . $cmd);
259  }
260 
266  public static function ‪addPaths(string ‪$paths): void
267  {
268  self::initPaths(‪$paths);
269  }
270 
277  public static function getPaths(bool $addInvalid = false): array
278  {
279  if (!self::init()) {
280  return [];
281  }
282 
283  return $addInvalid
285  : array_filter(self::$paths);
286  }
287 
291  protected static function init(): bool
292  {
293  if (‪$GLOBALS['TYPO3_CONF_VARS']['BE']['disable_exec_function']) {
294  return false;
295  }
296  if (!self::$initialized) {
297  self::initPaths();
298  self::$applications = self::getConfiguredApps();
299  self::$initialized = true;
300  }
301  return true;
302  }
303 
309  protected static function initPaths(string ‪$paths = ''): void
310  {
311  $doCheck = false;
312 
313  // Init global paths array if not already done
314  if (!is_array(self::$paths)) {
315  self::$paths = self::getPathsInternal();
316  $doCheck = true;
317  }
318  // Merge the submitted paths array to the global
319  if (‪$paths) {
321  foreach (‪$paths as $path) {
322  // Make absolute path of relative
323  if (!str_starts_with($path, '/')) {
324  $path = ‪Environment::getPublicPath() . '/' . $path;
325  }
326  if (!isset(self::$paths[$path])) {
327  if (@is_dir($path)) {
328  self::$paths[$path] = $path;
329  } else {
330  self::$paths[$path] = false;
331  }
332  }
333  }
334  }
335  // Check if new paths are invalid
336  if ($doCheck) {
337  foreach (self::$paths as $path => $valid) {
338  // Ignore invalid (FALSE) paths
339  if ($valid && !@is_dir($path)) {
340  self::$paths[$path] = false;
341  }
342  }
343  }
344  }
345 
351  protected static function getConfiguredApps(): array
352  {
353  $cmdArr = [];
354 
355  if (‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['binSetup']) {
356  $binSetup = str_replace(['\'.chr(10).\'', '\' . LF . \''], LF, ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['binSetup']);
357  $pathSetup = preg_split('/[\n,]+/', $binSetup);
358  foreach ($pathSetup as $val) {
359  if (trim($val) === '') {
360  continue;
361  }
362  [$cmd, $cmdPath] = ‪GeneralUtility::trimExplode('=', $val, true, 2);
363  $cmdArr[$cmd]['app'] = ‪PathUtility::basename($cmdPath);
364  $cmdArr[$cmd]['path'] = ‪PathUtility::dirname($cmdPath) . '/';
365  $cmdArr[$cmd]['valid'] = true;
366  }
367  }
368 
369  return $cmdArr;
370  }
371 
377  protected static function getPathsInternal(): array
378  {
379  $pathsArr = [];
380  $sysPathArr = [];
381 
382  // Image magick paths first
383  if ($imPath = ‪$GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_path']) {
384  $imPath = self::fixPath($imPath);
385  $pathsArr[$imPath] = $imPath;
386  }
387 
388  // Add configured paths
389  if (‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['binPath']) {
390  $sysPath = ‪GeneralUtility::trimExplode(',', ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['binPath'], true);
391  foreach ($sysPath as $val) {
392  $val = self::fixPath($val);
393  $sysPathArr[$val] = $val;
394  }
395  }
396 
397  // Add path from environment
398  if (!empty(‪$GLOBALS['_SERVER']['PATH']) || !empty(‪$GLOBALS['_SERVER']['Path'])) {
399  $sep = ‪Environment::isWindows() ? ';' : ':';
400  $serverPath = ‪$GLOBALS['_SERVER']['PATH'] ?? ‪$GLOBALS['_SERVER']['Path'];
401  $envPath = ‪GeneralUtility::trimExplode($sep, $serverPath, true);
402  foreach ($envPath as $val) {
403  $val = self::fixPath($val);
404  $sysPathArr[$val] = $val;
405  }
406  }
407 
408  // Set common paths for Unix (only)
409  if (!‪Environment::isWindows()) {
410  $sysPathArr = array_merge($sysPathArr, [
411  '/usr/bin/' => '/usr/bin/',
412  '/usr/local/bin/' => '/usr/local/bin/',
413  ]);
414  }
415 
416  return array_merge($pathsArr, $sysPathArr);
417  }
418 
425  protected static function fixPath(string $path): string
426  {
427  return str_replace('//', '/', $path . '/');
428  }
429 
438  public static function escapeShellArguments(array $input): array
439  {
440  $isUTF8Filesystem = !empty(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['UTF8filesystem']);
441  $currentLocale = false;
442  if ($isUTF8Filesystem) {
443  $currentLocale = setlocale(LC_CTYPE, '0');
444  setlocale(LC_CTYPE, ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['systemLocale']);
445  }
446 
447  ‪$output = array_map('escapeshellarg', $input);
448 
449  if ($isUTF8Filesystem && $currentLocale !== false) {
450  setlocale(LC_CTYPE, $currentLocale);
451  }
452 
453  return ‪$output;
454  }
455 
462  protected static function unQuoteFilenames(string $parameters): array
463  {
464  $paramsArr = explode(' ', trim($parameters));
465  // Whenever a quote character (") is found, $quoteActive is set to the element number inside of $params.
466  // A value of -1 means that there are not open quotes at the current position.
467  $quoteActive = -1;
468  foreach ($paramsArr as $k => $v) {
469  if ($quoteActive > -1) {
470  $paramsArr[$quoteActive] .= ' ' . $v;
471  unset($paramsArr[$k]);
472  if (substr($v, -1) === $paramsArr[$quoteActive][0]) {
473  $quoteActive = -1;
474  }
475  } elseif (!trim($v)) {
476  // Remove empty elements
477  unset($paramsArr[$k]);
478  } elseif (preg_match('/^(["\'])/', $v) && substr($v, -1) !== $v[0]) {
479  $quoteActive = $k;
480  }
481  }
482  // Return re-indexed array
483  return array_values($paramsArr);
484  }
485 
494  public static function escapeShellArgument(string $input): string
495  {
496  return self::escapeShellArguments([$input])[0];
497  }
498 }
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static list< string > trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
Definition: GeneralUtility.php:916
‪TYPO3\CMS\Core\Core\Environment\getPublicPath
‪static getPublicPath()
Definition: Environment.php:187
‪TYPO3\CMS\Core\Utility
Definition: ArrayUtility.php:18
‪TYPO3\CMS\Core\Utility\CommandUtility\checkCommand
‪static bool int checkCommand(string $cmd, string $handler='')
Definition: CommandUtility.php:164
‪TYPO3\CMS\Core\Utility\PathUtility\basename
‪static basename(string $path)
Definition: PathUtility.php:219
‪TYPO3\CMS\Core\Utility\PathUtility\dirname
‪static dirname(string $path)
Definition: PathUtility.php:243
‪TYPO3\CMS\Core\Utility\CommandUtility\$applications
‪static array $applications
Definition: CommandUtility.php:69
‪TYPO3\CMS\Core\Utility\CommandUtility\exec
‪static exec(string $command, ?array &$output=null, int &$returnValue=0)
Definition: CommandUtility.php:85
‪$output
‪$output
Definition: annotationChecker.php:119
‪TYPO3\CMS\Core\Utility\CommandUtility\getCommand
‪static string bool int getCommand(string $cmd, string $handler='', string $handlerOpt='')
Definition: CommandUtility.php:236
‪TYPO3\CMS\Core\Utility\CommandUtility\addPaths
‪static addPaths(string $paths)
Definition: CommandUtility.php:266
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\Core\Environment
Definition: Environment.php:41
‪TYPO3\CMS\Core\Utility\CommandUtility\imageMagickCommand
‪static string imageMagickCommand(string $command, string $parameters, string $path='')
Definition: CommandUtility.php:98
‪TYPO3\CMS\Core\Utility\CommandUtility\$initialized
‪static bool $initialized
Definition: CommandUtility.php:58
‪TYPO3\CMS\Core\Utility\CommandUtility
Definition: CommandUtility.php:54
‪TYPO3\CMS\Core\Utility\CommandUtility\$paths
‪static array $paths
Definition: CommandUtility.php:78
‪TYPO3\CMS\Core\Core\Environment\isWindows
‪static isWindows()
Definition: Environment.php:287