Test Failed
Push — main ( 961615...d5171d )
by Andreas
12:44 queued 13s
created

Escaper   A

Complexity

Total Complexity 14

Size/Duplication

Total Lines 107
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 14
eloc 33
c 1
b 0
f 0
dl 0
loc 107
rs 10

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A initializeUtf8Locale() 0 25 5
A shellCmd() 0 3 1
A escapeshellcmd() 0 16 3
A shellArg() 0 3 1
A escapeshellarg() 0 16 3
1
<?php
2
/**
3
 * Escaper.
4
 *
5
 * @copyright 2014-2024 Institute of Legal Medicine, Medical University of Innsbruck
6
 * @author Andreas Erhard <[email protected]>
7
 * @license LGPL-3.0-only
8
 * @link http://www.gerichtsmedizin.at/
9
 *
10
 * @package pdftk
11
 */
12
13
namespace Gmi\Toolkit\Pdftk\Util;
14
15
use Exception;
16
17
/**
18
 * Escaping helper class to fix shortcomings of the PHP escapeshellarg/escapeshellcmd functions.
19
 */
20
class Escaper
21
{
22
    /**
23
     *
24
     * @var string|true The best detected UTF-8 locale, or true if there was already a UTF-8 locale set.
25
     */
26
    private $locale = '';
27
28
    /**
29
     *
30
     * @param string[] $utf8Locales UTF-8 aware locales which should be used, in order of preference.
31
     *
32
     * @throws Exception if no
33
     */
34
    public function __construct(array $utf8Locales = ['de_AT.UTF-8', 'de_DE.UTF-8', 'en_US.UTF-8', 'C.UTF-8'])
35
    {
36
        $this->initializeUtf8Locale($utf8Locales);
37
    }
38
39
    /**
40
     * Escapes a shell argument.
41
     *
42
     * Wrapper around the PHP escapeshellarg() function which uses the correct locale for UTF-8 support.
43
     *
44
     * If the current locale does not support UTF-8, it switches to a supported UTF-8 locale
45
     * and back to the original one after the escape operation.
46
     */
47
    public function escapeshellarg(string $arg): string
48
    {
49
        $previousLocale = null;
50
51
        if (true !== $this->locale) {
52
            $previousLocale = setlocale(LC_CTYPE, 0);
53
            setlocale(LC_CTYPE, $this->locale);
0 ignored issues
show
Bug introduced by
It seems like $this->locale can also be of type false; however, parameter $locales of setlocale() does only seem to accept array|integer|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

53
            setlocale(LC_CTYPE, /** @scrutinizer ignore-type */ $this->locale);
Loading history...
54
        }
55
56
        $result = escapeshellarg($arg);
57
58
        if (true !== $this->locale) {
59
            setlocale(LC_CTYPE, $previousLocale);
0 ignored issues
show
Bug introduced by
It seems like $previousLocale can also be of type null; however, parameter $locales of setlocale() does only seem to accept array|integer|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

59
            setlocale(LC_CTYPE, /** @scrutinizer ignore-type */ $previousLocale);
Loading history...
60
        }
61
62
        return $result;
63
    }
64
65
    /**
66
     * Escapes a shell command, see Escaper::escapeshellarg().
67
     */
68
    public function escapeshellcmd(string $command): string
69
    {
70
        $previousLocale = '';
71
72
        if (true !== $this->locale) {
73
            $previousLocale = setlocale(LC_CTYPE, 0);
74
            setlocale(LC_CTYPE, $this->locale);
0 ignored issues
show
Bug introduced by
It seems like $this->locale can also be of type false; however, parameter $locales of setlocale() does only seem to accept array|integer|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

74
            setlocale(LC_CTYPE, /** @scrutinizer ignore-type */ $this->locale);
Loading history...
75
        }
76
77
        $result = escapeshellcmd($command);
78
79
        if (true !== $this->locale) {
80
            setlocale(LC_CTYPE, $previousLocale);
81
        }
82
83
        return $result;
84
    }
85
86
    /**
87
     * Alias for escapeshellarg().
88
     */
89
    public function shellArg(string $arg): string
90
    {
91
        return $this->escapeshellarg($arg);
92
    }
93
94
    /**
95
     * Alias for escapeshellcmd().
96
     */
97
    public function shellCmd(string $arg): string
98
    {
99
        return $this->escapeshellcmd($arg);
100
    }
101
102
    private function initializeUtf8Locale(array $utf8Locales): void
103
    {
104
        // with 0 as the second argument, setlocale() returns the current locale
105
        $previousLocale = setlocale(LC_CTYPE, 0);
106
107
        // keep the current locale, as it is already capable of UTF-8. Store true as sentinel value.
108
        if (false !== strpos($previousLocale, '.UTF-8')) {
109
            $this->locale = true;
110
111
            return;
112
        }
113
114
        $locale = false;
115
        foreach ($utf8Locales as $cur) {
116
            if (false !== @setlocale(LC_CTYPE, $cur)) {
117
                $locale = $cur;
118
                setlocale(LC_CTYPE, $previousLocale);
119
            }
120
        }
121
122
        if (!$locale) {
123
            throw new Exception('No supported UTF-8 locale found!');
124
        }
125
126
        $this->locale = $locale;
127
    }
128
}
129