This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
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 Percy\Http; |
||
4 | |||
5 | use InvalidArgumentException; |
||
6 | |||
7 | trait QueryStringParserTrait |
||
8 | { |
||
9 | /** |
||
10 | * @var integer |
||
11 | */ |
||
12 | protected $param = 0; |
||
13 | |||
14 | /** |
||
15 | * Parse HTTP query string and return array representation |
||
16 | * to be attached to a database query. |
||
17 | * |
||
18 | * @param string $query |
||
19 | * |
||
20 | * @return array |
||
21 | */ |
||
22 | 21 | public function parseQueryString($query) |
|
23 | { |
||
24 | 21 | if (empty($query)) { |
|
25 | 3 | return []; |
|
26 | } |
||
27 | |||
28 | 19 | parse_str($query, $split); |
|
29 | |||
30 | 19 | $query = []; |
|
31 | |||
32 | 19 | foreach ($split as $key => $value) { |
|
0 ignored issues
–
show
|
|||
33 | 19 | $mapped = call_user_func_array([$this, 'filterQueryParams'], [$key, $value]); |
|
34 | 15 | if ($mapped !== false) { |
|
35 | 14 | $query[$key] = $mapped; |
|
36 | 14 | } |
|
37 | 15 | } |
|
38 | |||
39 | 15 | return $query; |
|
40 | } |
||
41 | |||
42 | /** |
||
43 | * Map the parsed query string in to correct array structure. |
||
44 | * |
||
45 | * @param string $key |
||
46 | * @param mixed $value |
||
47 | * |
||
48 | * @return array|boolean |
||
49 | */ |
||
50 | 19 | protected function filterQueryParams($key, $value) |
|
51 | { |
||
52 | switch ($key) { |
||
53 | 19 | case 'limit': |
|
54 | 19 | case 'offset': |
|
55 | 1 | return (int) $value; |
|
56 | 19 | case 'sort': |
|
57 | 7 | return $this->parseSort($value); |
|
58 | 17 | case 'filter': |
|
59 | 10 | return $this->parseFilters((array) $value); |
|
60 | 9 | case 'has': |
|
61 | 2 | return explode(',', $value); |
|
62 | 8 | case 'include': |
|
63 | 3 | return $this->parseInclude(explode(',', $value)); |
|
64 | 5 | case 'search': |
|
65 | 4 | return $this->parseSearch($value); |
|
66 | 4 | case 'minscore': |
|
67 | 3 | return (float) $value; |
|
68 | 1 | default: |
|
69 | 1 | return false; |
|
70 | 1 | } |
|
71 | } |
||
72 | |||
73 | /** |
||
74 | * Map sorts to a usable format. |
||
75 | * |
||
76 | * @param string $value |
||
77 | * |
||
78 | * @return array |
||
79 | */ |
||
80 | 7 | protected function parseSort($value) |
|
81 | { |
||
82 | 7 | $map = []; |
|
83 | 7 | $sorts = explode(',', $value); |
|
84 | |||
85 | 7 | foreach ($sorts as $sort) { |
|
86 | 7 | $sort = explode('|', $sort); |
|
87 | 7 | $direction = (count($sort) > 1) ? $sort[1] : 'asc'; |
|
88 | |||
89 | 7 | if (in_array($sort[0], ['rand', 'random'])) { |
|
90 | 2 | return 'RAND()'; |
|
91 | } |
||
92 | |||
93 | 6 | $map[] = [ |
|
94 | 6 | 'field' => $sort[0], |
|
95 | 'direction' => $direction |
||
96 | 6 | ]; |
|
97 | 6 | } |
|
98 | |||
99 | 6 | return $map; |
|
100 | } |
||
101 | |||
102 | /** |
||
103 | * Map search to a usable format. |
||
104 | * |
||
105 | * @param string $value |
||
106 | * |
||
107 | * @return array |
||
108 | */ |
||
109 | 4 | protected function parseSearch($value) |
|
110 | { |
||
111 | 4 | $search = explode('|', $value); |
|
112 | |||
113 | 4 | if (count($search) !== 2) { |
|
114 | 1 | throw new InvalidArgumentException( |
|
115 | 'Malformed query string, search format should be (search=field|term) or (search=field1,field2|term)' |
||
116 | 1 | ); |
|
117 | } |
||
118 | |||
119 | return [ |
||
120 | 3 | 'fields' => $search[0], |
|
121 | 3 | 'term' => $search[1] |
|
122 | 3 | ]; |
|
123 | } |
||
124 | |||
125 | /** |
||
126 | * Map filters in to useable array. |
||
127 | * |
||
128 | * @param array $filters |
||
129 | * |
||
130 | * @return array |
||
131 | */ |
||
132 | 10 | protected function parseFilters(array $filters) |
|
133 | { |
||
134 | 10 | $mapped = []; |
|
135 | |||
136 | 10 | $this->param = 0; |
|
137 | |||
138 | 10 | foreach ($filters as $filter) { |
|
139 | 10 | $mapped[] = $this->parseFilter($filter); |
|
140 | 8 | } |
|
141 | |||
142 | 8 | return $mapped; |
|
143 | } |
||
144 | |||
145 | /** |
||
146 | * Parse an individual filter. |
||
147 | * |
||
148 | * @param string $filter |
||
149 | * |
||
150 | * @return array |
||
151 | */ |
||
152 | 12 | protected function parseFilter($filter) |
|
153 | { |
||
154 | 12 | $filter = explode('|', $filter); |
|
155 | |||
156 | 12 | if (count($filter) !== 3) { |
|
157 | 1 | throw new InvalidArgumentException( |
|
158 | 'Malformed query string, filter format should be (filter[]=field|delimiter|value)' |
||
159 | 1 | ); |
|
160 | } |
||
161 | |||
162 | 11 | $filter = array_combine(['field', 'delimiter', 'value'], $filter); |
|
163 | |||
164 | 11 | $filter['binding'] = str_replace('.', '_', $filter['field']) . '_' . $this->param++; |
|
165 | 11 | $filter['delimiter'] = strtolower($filter['delimiter']); |
|
166 | 11 | $filter['delimiter'] = html_entity_decode($filter['delimiter']); |
|
167 | |||
168 | 11 | if (! in_array($filter['delimiter'], [ |
|
169 | 11 | '=', '!=', '<>', '<=', '>=', '<', '>', 'in', 'not in', 'like', 'not like' |
|
170 | 11 | ])) { |
|
171 | 2 | throw new InvalidArgumentException(sprintf('(%s) is not an accepted delimiter', $filter['delimiter'])); |
|
172 | } |
||
173 | |||
174 | 10 | return $filter; |
|
175 | } |
||
176 | |||
177 | /** |
||
178 | * Map includes in to useable array. |
||
179 | * |
||
180 | * @param array $includes |
||
181 | * |
||
182 | * @return array |
||
183 | */ |
||
184 | 3 | protected function parseInclude(array $includes) |
|
185 | { |
||
186 | 3 | $mapped = []; |
|
187 | |||
188 | 3 | foreach ($includes as $include) { |
|
189 | 3 | $parts = explode(';', $include); |
|
190 | 3 | $key = array_shift($parts); |
|
191 | |||
192 | 3 | if (empty($parts)) { |
|
193 | 3 | $mapped[$key] = []; |
|
194 | 3 | continue; |
|
195 | } |
||
196 | |||
197 | 3 | $mapped[$key]['filter'] = []; |
|
198 | 3 | $mapped[$key]['limit'] = null; |
|
199 | |||
200 | 3 | foreach ($parts as $part) { |
|
201 | 3 | $split = explode('|', $part); |
|
202 | |||
203 | 3 | switch (count($split)) { |
|
204 | 3 | case 2: |
|
205 | 1 | $mapped[$key]['limit'] = $split[1]; |
|
206 | 1 | break; |
|
207 | 3 | case 3: |
|
208 | 2 | $mapped[$key]['filter'][] = $this->parseFilter($part); |
|
209 | 2 | break; |
|
210 | 1 | default: |
|
211 | 1 | throw new InvalidArgumentException('Include formatted incorrectly.'); |
|
212 | 3 | } |
|
213 | 2 | } |
|
214 | 2 | } |
|
215 | |||
216 | 2 | return $mapped; |
|
217 | } |
||
218 | } |
||
219 |
There are different options of fixing this problem.
If you want to be on the safe side, you can add an additional type-check:
If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:
Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.