Issues (40)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Translator.php (2 issues)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace Gettext;
4
5
use Gettext\Generators\PhpArray;
6
7
class Translator extends BaseTranslator implements TranslatorInterface
8
{
9
    protected $domain;
10
    protected $dictionary = [];
11
    protected $plurals = [];
12
13
    /**
14
     * Loads translation from a Translations instance, a file on an array.
15
     *
16
     * @param Translations|string|array $translations
17
     *
18
     * @return static
19
     */
20
    public function loadTranslations($translations)
21
    {
22
        if (is_object($translations) && $translations instanceof Translations) {
23
            $translations = PhpArray::generate($translations, ['includeHeaders' => false]);
24
        } elseif (is_string($translations) && is_file($translations)) {
25
            $translations = include $translations;
26
        } elseif (!is_array($translations)) {
27
            throw new \InvalidArgumentException(
28
                'Invalid Translator: only arrays, files or instance of Translations are allowed'
29
            );
30
        }
31
32
        $this->addTranslations($translations);
33
34
        return $this;
35
    }
36
37
    /**
38
     * Set the default domain.
39
     *
40
     * @param string $domain
41
     *
42
     * @return static
43
     */
44
    public function defaultDomain($domain)
45
    {
46
        $this->domain = $domain;
47
48
        return $this;
49
    }
50
51
    /**
52
     * @see TranslatorInterface
53
     *
54
     * {@inheritdoc}
55
     */
56
    public function gettext($original)
57
    {
58
        return $this->dpgettext($this->domain, null, $original);
59
    }
60
61
    /**
62
     * @see TranslatorInterface
63
     *
64
     * {@inheritdoc}
65
     */
66
    public function ngettext($original, $plural, $value)
67
    {
68
        return $this->dnpgettext($this->domain, null, $original, $plural, $value);
69
    }
70
71
    /**
72
     * @see TranslatorInterface
73
     *
74
     * {@inheritdoc}
75
     */
76
    public function dngettext($domain, $original, $plural, $value)
77
    {
78
        return $this->dnpgettext($domain, null, $original, $plural, $value);
79
    }
80
81
    /**
82
     * @see TranslatorInterface
83
     *
84
     * {@inheritdoc}
85
     */
86
    public function npgettext($context, $original, $plural, $value)
87
    {
88
        return $this->dnpgettext($this->domain, $context, $original, $plural, $value);
89
    }
90
91
    /**
92
     * @see TranslatorInterface
93
     *
94
     * {@inheritdoc}
95
     */
96
    public function pgettext($context, $original)
97
    {
98
        return $this->dpgettext($this->domain, $context, $original);
99
    }
100
101
    /**
102
     * @see TranslatorInterface
103
     *
104
     * {@inheritdoc}
105
     */
106
    public function dgettext($domain, $original)
107
    {
108
        return $this->dpgettext($domain, null, $original);
109
    }
110
111
    /**
112
     * @see TranslatorInterface
113
     *
114
     * {@inheritdoc}
115
     */
116
    public function dpgettext($domain, $context, $original)
117
    {
118
        $translation = $this->getTranslation($domain, $context, $original);
119
120
        if (isset($translation[0]) && $translation[0] !== '') {
121
            return $translation[0];
122
        }
123
124
        return $original;
125
    }
126
127
    /**
128
     * @see TranslatorInterface
129
     *
130
     * {@inheritdoc}
131
     */
132
    public function dnpgettext($domain, $context, $original, $plural, $value)
133
    {
134
        $translation = $this->getTranslation($domain, $context, $original);
135
        $key = $this->getPluralIndex($domain, $value, $translation === false);
136
137
        if (isset($translation[$key]) && $translation[$key] !== '') {
138
            return $translation[$key];
139
        }
140
141
        return ($key === 0) ? $original : $plural;
142
    }
143
144
    /**
145
     * Set new translations to the dictionary.
146
     *
147
     * @param array $translations
148
     */
149
    protected function addTranslations(array $translations)
150
    {
151
        $domain = isset($translations['domain']) ? $translations['domain'] : '';
152
153
        //Set the first domain loaded as default domain
154
        if ($this->domain === null) {
155
            $this->domain = $domain;
156
        }
157
158
        if (isset($this->dictionary[$domain])) {
159
            $this->dictionary[$domain] = array_replace_recursive($this->dictionary[$domain], $translations['messages']);
160
161
            return;
162
        }
163
164
        if (!empty($translations['plural-forms'])) {
165
            list($count, $code) = array_map('trim', explode(';', $translations['plural-forms'], 2));
166
167
            // extract just the expression turn 'n' into a php variable '$n'.
168
            // Slap on a return keyword and semicolon at the end.
169
            $this->plurals[$domain] = [
170
                'count' => (int) str_replace('nplurals=', '', $count),
171
                'code' => str_replace('plural=', 'return ', str_replace('n', '$n', $code)).';',
172
            ];
173
        }
174
175
        $this->dictionary[$domain] = $translations['messages'];
176
    }
177
178
    /**
179
     * Search and returns a translation.
180
     *
181
     * @param string $domain
182
     * @param string $context
183
     * @param string $original
184
     *
185
     * @return string|false
186
     */
187
    protected function getTranslation($domain, $context, $original)
188
    {
189
        return isset($this->dictionary[$domain][$context][$original])
190
             ? $this->dictionary[$domain][$context][$original]
191
             : false;
192
    }
193
194
    /**
195
     * Executes the plural decision code given the number to decide which
196
     * plural version to take.
197
     *
198
     * @param string $domain
199
     * @param string $n
200
     * @param bool   $fallback set to true to get fallback plural index
201
     *
202
     * @return int
203
     */
204
    protected function getPluralIndex($domain, $n, $fallback)
205
    {
206
        //Not loaded domain or translation, use a fallback
207
        if (!isset($this->plurals[$domain]) || $fallback === true) {
208
            return $n == 1 ? 0 : 1;
209
        }
210
211
        if (!isset($this->plurals[$domain]['function'])) {
212
            $code = static::fixTerseIfs($this->plurals[$domain]['code']);
0 ignored issues
show
Since fixTerseIfs() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of fixTerseIfs() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
213
            $this->plurals[$domain]['function'] = eval("return function (\$n) { $code };");
214
        }
215
216
        if ($this->plurals[$domain]['count'] <= 2) {
217
            return call_user_func($this->plurals[$domain]['function'], $n) ? 1 : 0;
218
        }
219
220
        return call_user_func($this->plurals[$domain]['function'], $n);
221
    }
222
223
    /**
224
     * This function will recursively wrap failure states in brackets if they contain a nested terse if.
225
     *
226
     * This because PHP can not handle nested terse if's unless they are wrapped in brackets.
227
     *
228
     * This code probably only works for the gettext plural decision codes.
229
     *
230
     * return ($n==1 ? 0 : $n%10>=2 && $n%10<=4 && ($n%100<10 || $n%100>=20) ? 1 : 2);
231
     * becomes
232
     * return ($n==1 ? 0 : ($n%10>=2 && $n%10<=4 && ($n%100<10 || $n%100>=20) ? 1 : 2));
233
     *
234
     * @param string $code  the terse if string
235
     * @param bool   $inner If inner is true we wrap it in brackets
236
     *
237
     * @return string A formatted terse If that PHP can work with.
238
     */
239
    private static function fixTerseIfs($code, $inner = false)
240
    {
241
        /*
242
         * (?P<expression>[^?]+)   Capture everything up to ? as 'expression'
243
         * \?                      ?
244
         * (?P<success>[^:]+)      Capture everything up to : as 'success'
245
         * :                       :
246
         * (?P<failure>[^;]+)      Capture everything up to ; as 'failure'
247
         */
248
        preg_match('/(?P<expression>[^?]+)\?(?P<success>[^:]+):(?P<failure>[^;]+)/', $code, $matches);
249
250
        // If no match was found then no terse if was present
251
        if (!isset($matches[0])) {
252
            return $code;
253
        }
254
255
        $expression = $matches['expression'];
256
        $success = $matches['success'];
257
        $failure = $matches['failure'];
258
259
        // Go look for another terse if in the failure state.
260
        $failure = static::fixTerseIfs($failure, true);
0 ignored issues
show
Since fixTerseIfs() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of fixTerseIfs() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
261
        $code = $expression.' ? '.$success.' : '.$failure;
262
263
        if ($inner) {
264
            return "($code)";
265
        }
266
267
        // note the semicolon. We need that for executing the code.
268
        return "$code;";
269
    }
270
}
271