Issues (5)

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/Filtrator.php (1 issue)

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
declare(strict_types=1);
3
namespace Sirius\Filtration;
4
5
class Filtrator implements FiltratorInterface
6
{
7
    // selector to specify that the filter is applied to the entire data set
8
    const SELECTOR_ROOT = '/';
9
10
    // selector to specify that the filter is applied to all ITEMS of a set
11
    const SELECTOR_ANY = '*';
12
13
    protected $filterFactory;
14
15
    /**
16
     * The list of filters available in the filtrator
17
     *
18
     * @var array
19
     */
20
    protected $filters = [];
21
22
    /**
23
     * @var array
24
     */
25
    protected $allowedSelectors = [];
26
27
    /**
28
     * @var array
29
     */
30
    protected $compiledAllowedSelectors = [];
31
32 20
    public function __construct(FilterFactory $filterFactory = null)
33
    {
34 20
        if (!$filterFactory) {
35
            $filterFactory = new FilterFactory();
36
        }
37 20
        $this->filterFactory = $filterFactory;
38 20
    }
39
40 1
    public function setAllowed(array $allowedSelectors = [])
41
    {
42 1
        $this->allowedSelectors = $allowedSelectors;
43 1
    }
44
45
    /**
46
     * Add a filter to the filters stack
47
     *
48
     * @example // normal callback
49
     *          $filtrator->add('title', '\strip_tags');
50
     *          // anonymous function
51
     *          $filtrator->add('title', function($value){ return $value . '!!!'; });
52
     *          // filter class from the library registered on the $filtersMap
53
     *          $filtrator->add('title', 'normalizedate', ['format' => 'm/d/Y']);
54
     *          // custom class
55
     *          $filtrator->add('title', '\MyApp\Filters\CustomFilter');
56
     *          // multiple filters as once with different ways to pass options
57
     *          $filtrator->add('title', [
58
     *              ['truncate', 'limit=10', true, 10],
59
     *              ['censor', ['words' => ['idiot']]
60
     *          ]);
61
     *          // multiple fitlers as a single string
62
     *          $filtrator->add('title', 'stringtrim(side=left)(true)(10) | truncate(limit=100)');
63
     * @param string|array $selector
64
     * @param mixed $callbackOrFilterName
65
     * @param array|null $options
66
     * @param bool $recursive
67
     * @param integer $priority
68
     * @throws \InvalidArgumentException
69
     * @internal param $ callable|filter class name|\Sirius\Filtration\Filter\AbstractFilter $callbackOrFilterName
70
     * @internal param array|string $params
71
     * @return self
72
     */
73 20
    public function add($selector, $callbackOrFilterName = null, $options = null, $recursive = false, $priority = 0)
74
    {
75
        /**
76
         * $selector is actually an array with filters
77
         *
78
         * @example $filtrator->add([
79
         *              'title' => ['trim', ['truncate', '{"limit":100}']]
80
         *              'description' => ['trim']
81
         *          ]);
82
         */
83 20
        if (is_array($selector)) {
84 1
            foreach ($selector as $key => $filters) {
85 1
                $this->add($key, $filters);
86
            }
87 1
            return $this;
88
        }
89
90 20
        if (! is_string($selector)) {
91 1
            throw new \InvalidArgumentException('The data selector for filtering must be a string');
92
        }
93
94
95 19
        if (is_string($callbackOrFilterName)) {
96
            // rule was supplied like 'trim' or 'trim | nullify'
97 15
            if (strpos($callbackOrFilterName, ' | ') !== false) {
98 1
                return $this->add($selector, explode(' | ', $callbackOrFilterName));
99
            }
100
            // rule was supplied like this 'trim(limit=10)(true)(10)'
101 15
            if (strpos($callbackOrFilterName, '(') !== false) {
102 2
                list($callbackOrFilterName, $options, $recursive, $priority) = $this->parseRule($callbackOrFilterName);
103
            }
104
        }
105
106
        /**
107
         * The $callbackOrFilterName is an array of filters
108
         *
109
         * @example $filtrator->add('title', [
110
         *          'trim',
111
         *          ['truncate', '{"limit":100}']
112
         *      ]);
113
         */
114 19
        if (is_array($callbackOrFilterName) && ! is_callable($callbackOrFilterName)) {
115 3
            foreach ($callbackOrFilterName as $filter) {
116
                // $filter is something like ['truncate', '{"limit":100}']
117 3
                if (is_array($filter) && ! is_callable($filter)) {
118 1
                    $this->add($selector, ...$filter);
119 2
                } elseif (is_string($filter) || is_callable($filter)) {
120 2
                    $this->add($selector, $filter);
121
                }
122
            }
123 3
            return $this;
124
        }
125
126 19
        $filter = $this->filterFactory->createFilter($callbackOrFilterName, $options, $recursive);
127 16
        if (! array_key_exists($selector, $this->filters)) {
128 16
            $this->filters[$selector] = new FilterSet();
129
        }
130
        /* @var $filterSet FilterSet */
131 16
        $filterSet = $this->filters[$selector];
132 16
        $filterSet->insert($filter, $priority);
133 16
        $this->compiledAllowedSelectors = [];
134
135 16
        return $this;
136
    }
137
138
    /**
139
     * Converts a rule that was supplied as string into a set of options that define the rule
140
     *
141
     * @example 'minLength({"min":2})(true)(10)'
142
     *
143
     *          will be converted into
144
     *
145
     *          [
146
     *          'minLength', // validator name
147
     *          ['min' => 2'], // validator options
148
     *          true, // recursive
149
     *          10 // priority
150
     *          ]
151
     * @param string $ruleAsString
152
     * @return array
153
     */
154 2
    protected function parseRule($ruleAsString)
155
    {
156 2
        $ruleAsString = trim($ruleAsString);
157
158 2
        $options = [];
159 2
        $recursive = false;
160 2
        $priority = 0;
161
162 2
        $name = substr($ruleAsString, 0, strpos($ruleAsString, '('));
163 2
        $ruleAsString = substr($ruleAsString, strpos($ruleAsString, '('));
164 2
        $matches = [];
165 2
        preg_match_all('/\(([^\)]*)\)/', $ruleAsString, $matches);
166
167 2
        if (isset($matches[1])) {
168 2
            if (isset($matches[1][0]) && $matches[1][0]) {
169 2
                $options = $matches[1][0];
170
            }
171 2
            if (isset($matches[1][1]) && $matches[1][1]) {
172 2
                $recursive = (in_array($matches[1][1], array(true, 'TRUE', 'true', 1))) ? true : false;
173
            }
174 2
            if (isset($matches[1][2]) && $matches[1][2]) {
175 2
                $priority = (int)$matches[1][2];
176
            }
177
        }
178
179
        return [
180 2
            $name,
181 2
            $options,
182 2
            $recursive,
183 2
            $priority
184
        ];
185
    }
186
187
    /**
188
     * Remove a filter from the stack
189
     *
190
     * @param string $selector
191
     * @param bool|callable|string|TRUE $callbackOrName
192
     * @throws \InvalidArgumentException
193
     * @return \Sirius\Filtration\Filtrator
194
     */
195 3
    public function remove($selector, $callbackOrName = true)
196
    {
197 3
        if (array_key_exists($selector, $this->filters)) {
198 3
            if ($callbackOrName === true) {
199 1
                unset($this->filters[$selector]);
200
            } else {
201 2
                if (! is_object($callbackOrName)) {
202 1
                    $filter = $this->filterFactory->createFilter($callbackOrName);
0 ignored issues
show
It seems like $callbackOrName defined by parameter $callbackOrName on line 195 can also be of type boolean; however, Sirius\Filtration\FilterFactory::createFilter() does only seem to accept callable, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
203
                } else {
204 1
                    $filter = $callbackOrName;
205
                }
206
                /* @var $filterSet FilterSet */
207 2
                $filterSet = $this->filters[$selector];
208 2
                $filterSet->remove($filter);
209
            }
210
        }
211 3
        $this->compiledAllowedSelectors = [];
212
213 3
        return $this;
214
    }
215
216
    /**
217
     * Retrieve all filters stack
218
     *
219
     * @return array
220
     */
221 1
    public function getFilters()
222
    {
223 1
        return $this->filters;
224
    }
225
226
    /**
227
     * Apply filters to an array
228
     *
229
     * @param array $data
230
     * @return array
231
     */
232 15
    public function filter(array $data = [])
233
    {
234
        // first apply the filters to the ROOT
235 15
        if (isset($this->filters[self::SELECTOR_ROOT])) {
236
            /* @var $rootFilters FilterSet */
237 1
            $rootFilters = $this->filters[self::SELECTOR_ROOT];
238 1
            $data = $rootFilters->applyFilters($data);
239
        }
240
241 15
        $this->compileAllowedSelectors();
242
243 15
        $result = [];
244 15
        foreach ($data as $key => $value) {
245 15
            if ($this->itemIsAllowed($key)) {
246 15
                $result[$key] = $this->filterItem($data, $key);
247
            }
248
        }
249 15
        return $result;
250
    }
251
252
    /**
253
     * Apply filters on a single item in the array
254
     *
255
     * @param array $data
256
     * @param string $valueIdentifier
257
     * @return mixed
258
     */
259 15
    public function filterItem($data, $valueIdentifier)
260
    {
261 15
        $value = Utils::arrayGetByPath($data, $valueIdentifier);
262 15
        $value = $this->applyFilters($value, $valueIdentifier, $data);
263 15
        if (is_array($value)) {
264 5
            $result = [];
265 5
            foreach (array_keys($value) as $k) {
266 5
                if ($this->itemIsAllowed("{$valueIdentifier}[{$k}]")) {
267 5
                    $result[$k] = $this->filterItem($data, "{$valueIdentifier}[{$k}]");
268
                }
269
            }
270 5
            return $result;
271
        }
272 15
        return $value;
273
    }
274
275
    /**
276
     * Apply filters to a single value
277
     *
278
     * @param mixed $value
279
     *            value of the item
280
     * @param string $valueIdentifier
281
     *            array element path (eg: 'key' or 'key[0][subkey]')
282
     * @param mixed $context
283
     * @return mixed
284
     */
285 15
    public function applyFilters($value, $valueIdentifier, $context)
286
    {
287 15
        foreach ($this->filters as $selector => $filterSet) {
288
            /* @var $filterSet FilterSet */
289 15
            if ($selector != self::SELECTOR_ROOT && Utils::itemMatchesSelector($valueIdentifier, $selector)) {
290 14
                $value = $filterSet->applyFilters($value, $valueIdentifier, $context);
291
            }
292
        }
293 15
        return $value;
294
    }
295
296 15
    private function itemIsAllowed($item)
297
    {
298 15
        if (empty($this->compiledAllowedSelectors)) {
299 4
            return true;
300
        }
301 11
        foreach ($this->compiledAllowedSelectors as $selector) {
302 11
            if (Utils::itemMatchesSelector($item, $selector)) {
303 11
                return true;
304
            }
305
        }
306 3
        return false;
307
    }
308
309 15
    private function compileAllowedSelectors()
310
    {
311 15
        if (!empty($this->compiledAllowedSelectors)) {
312
            return;
313
        }
314
315 15
        $selectors = array_unique(array_merge(
316 15
            array_values($this->allowedSelectors),
317 15
            array_keys($this->filters)
318
        ));
319
320 15
        $compiled = [];
321
322 15
        foreach ($selectors as $selector) {
323 15
            if ($selector == '/' || $selector == '*') {
324 4
                continue;
325
            }
326 11
            $compiled[] = $selector;
327 11
            while ($lastPart = strrpos($selector, '[')) {
328 2
                $parent = substr($selector, 0, $lastPart);
329 2
                if (!in_array($parent, $compiled)) {
330 2
                    $compiled[] = $parent;
331
                }
332 2
                $selector = $parent;
333
            }
334
        }
335
336 15
        $this->compiledAllowedSelectors = $compiled;
337 15
    }
338
}
339