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