Test Failed
Push — feature/pdfcpu ( 14c728 )
by Andreas
09:40
created

Escaper::initializeUtf8Locale()   A

Complexity

Conditions 5
Paths 7

Size

Total Lines 25
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 12
c 1
b 0
f 0
dl 0
loc 25
rs 9.5555
cc 5
nc 7
nop 1
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|bool The best detected UTF-8 locale, or true if there was already a UTF-8 locale set.
25
     */
26
    private $locale = false;
27
28
    /**
29
     *
30
     * @param string[] UTF-8 aware locales which should be used, in order of preference.
0 ignored issues
show
Documentation Bug introduced by
The doc comment UTF-8 at position 0 could not be parsed: Unknown type name 'UTF-8' at position 0 in UTF-8.
Loading history...
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
        if (true !== $this->locale) {
50
            $previousLocale = setlocale(LC_CTYPE, 0);
51
            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

51
            setlocale(LC_CTYPE, /** @scrutinizer ignore-type */ $this->locale);
Loading history...
52
        }
53
54
        $result = escapeshellarg($arg);
55
56
        if (true !== $this->locale) {
57
            setlocale(LC_CTYPE, $previousLocale);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $previousLocale does not seem to be defined for all execution paths leading up to this point.
Loading history...
58
        }
59
60
        return $result;
61
    }
62
63
    /**
64
     * Escapes a shell command, see Escaper::escapeshellarg().
65
     */
66
    public function escapeshellcmd(string $command): string
67
    {
68
        if (true !== $this->locale) {
69
            $previousLocale = setlocale(LC_CTYPE, 0);
70
            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

70
            setlocale(LC_CTYPE, /** @scrutinizer ignore-type */ $this->locale);
Loading history...
71
        }
72
73
        $result = escapeshellcmd($command);
74
75
        if (true !== $this->locale) {
76
            setlocale(LC_CTYPE, $previousLocale);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $previousLocale does not seem to be defined for all execution paths leading up to this point.
Loading history...
77
        }
78
79
        return $result;
80
    }
81
82
    /**
83
     * Alias for escapeshellarg().
84
     */
85
    public function shellArg(string $arg): string
86
    {
87
        return $this->escapeshellarg($arg);
88
    }
89
90
    /**
91
     * Alias for escapeshellcmd().
92
     */
93
    public function shellCmd(string $arg): string
94
    {
95
        return $this->escapeshellcmd($arg);
96
    }
97
98
    private function initializeUtf8Locale($utf8Locales)
99
    {
100
        // with 0 as the second argument, setlocale() returns the current locale
101
        $previousLocale = setlocale(LC_CTYPE, 0);
102
103
        // keep the current locale, as it is already capable of UTF-8. Store true as sentinel value.
104
        if (false !== strpos($previousLocale, '.UTF-8')) {
105
            $this->locale = true;
106
107
            return;
108
        }
109
110
        $locale = false;
111
        foreach ($utf8Locales as $cur) {
112
            if (false !== @setlocale(LC_CTYPE, $cur)) {
113
                $locale = $cur;
114
                setlocale(LC_CTYPE, $previousLocale);
115
            }
116
        }
117
118
        if (!$locale) {
119
            throw new Exception('No supported UTF-8 locale found!');
120
        }
121
122
        $this->locale = $locale;
123
    }
124
}
125