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 rdx\fuelly; |
||
4 | |||
5 | use InvalidArgumentException; |
||
6 | use rdx\fuelly\FuelUp; |
||
7 | use rdx\fuelly\Vehicle; |
||
8 | use rdx\fuelly\WebAuth; |
||
9 | use rdx\http\HTTP; |
||
10 | use rdx\jsdom\Node; |
||
11 | |||
12 | class Client { |
||
13 | |||
14 | public $base = 'http://www.fuelly.com/'; |
||
15 | public $loginBase = 'https://m.fuelly.com/'; |
||
16 | |||
17 | public $dateFormat = 'd/m/Y'; |
||
18 | public $timeFormat = 'g:i a'; |
||
19 | |||
20 | public $auth; // rdx\fuelly\WebAuth |
||
21 | public $input; // rdx\fuelly\InputConversion |
||
22 | public $username = ''; |
||
23 | public $vehicles = array(); |
||
24 | |||
25 | public $log = array(); |
||
26 | |||
27 | /** |
||
28 | * Dependency constructor |
||
29 | */ |
||
30 | public function __construct( WebAuth $auth, InputConversion $input ) { |
||
31 | $this->auth = $auth; |
||
32 | $this->input = $input; |
||
33 | } |
||
34 | |||
35 | /** |
||
36 | * |
||
37 | */ |
||
38 | public function createTrendInputConversion() { |
||
39 | // Trend is always in real numbers, and only its natives are reliable so use those |
||
40 | return new InputConversion('ml', 'usg', $this->input->mileage, '.', ','); |
||
41 | } |
||
42 | |||
43 | /** |
||
44 | * |
||
45 | */ |
||
46 | public function getFuelUp( $id ) { |
||
47 | $fuelup = compact('id'); |
||
48 | |||
49 | $response = $this->_get('fuelups/' . $id . '/edit'); |
||
50 | |||
51 | preg_match_all('#<input[\s\S]+?name="([^"]+)"[\s\S]+?>#', $response->body, $matches, PREG_SET_ORDER); |
||
52 | foreach ( $matches as $match ) { |
||
53 | if ( in_array($match[1], array('_token', 'miles_last_fuelup', 'price_per_unit', 'amount', 'fuelup_date')) ) { |
||
54 | preg_match('#value="([^"]+)"#', $match[0], $match2); |
||
55 | $fuelup[ $match[1] ] = $match2[1]; |
||
56 | } |
||
57 | } |
||
58 | |||
59 | preg_match('#<textarea[^>]+name="note"[^>]*>([^>]+)</textarea>#', $response->body, $match); |
||
60 | $fuelup['note'] = trim(@$match[1]); |
||
61 | |||
62 | return $fuelup; |
||
63 | } |
||
64 | |||
65 | /** |
||
66 | * |
||
67 | */ |
||
68 | public function updateFuelUp( $id, $data ) { |
||
69 | $data = $this->_validateFuelUpData($data); |
||
70 | unset($data['id']); |
||
71 | |||
72 | if ( !isset($data['_token']) ) { |
||
73 | $response = $this->_get('fuelups/' . $id . '/edit'); |
||
74 | $data['_token'] = $this->extractFormToken($response->body); |
||
75 | } |
||
76 | |||
77 | $data['_method'] = 'PUT'; |
||
78 | $response = $this->_post('fuelups/' . $id, array( |
||
79 | 'data' => $data, |
||
80 | )); |
||
81 | return $response->code == 302; |
||
82 | } |
||
83 | |||
84 | /** |
||
85 | * |
||
86 | */ |
||
87 | public function _validateFuelUpData( $data ) { |
||
88 | $data += array( |
||
89 | 'errorlevel' => 2, |
||
90 | 'price_per_unit' => '', |
||
91 | 'cost' => '', |
||
92 | 'city_pct' => '0', |
||
93 | 'fueltype_id' => '', |
||
94 | 'fuelup_date' => date($this->dateFormat), |
||
95 | 'time' => date($this->timeFormat), |
||
96 | 'paymenttype_id' => '', |
||
97 | 'fuelbrand' => '', |
||
98 | 'tirepsi' => '', |
||
99 | 'note' => '', |
||
100 | ); |
||
101 | |||
102 | $required = array('usercar_id', 'miles_last_fuelup', 'amount'); |
||
103 | $missing = array_diff($required, array_keys(array_filter($data))); |
||
104 | if ( $missing ) { |
||
0 ignored issues
–
show
|
|||
105 | throw new InvalidArgumentException('Missing params: ' . implode(', ', $missing)); |
||
106 | } |
||
107 | |||
108 | return $data; |
||
109 | } |
||
110 | |||
111 | /** |
||
112 | * |
||
113 | */ |
||
114 | public function getFuelUpsWithIds( Vehicle $vehicle, $limit = 15 ) { |
||
115 | $query = http_build_query(array( |
||
116 | 'iDisplayStart' => 0, |
||
117 | 'iDisplayLength' => $limit, |
||
118 | 'sSortDir_0' => 'desc', |
||
119 | 'usercar_id' => $vehicle->id, |
||
120 | )); |
||
121 | $response = $this->_get('ajax/fuelup-log?' . $query); |
||
122 | if ( $response->code == 200 ) { |
||
123 | if ( $response->response ) { |
||
124 | $fuelups = array(); |
||
125 | foreach ( $response->response['aaData'] as $fuelup ) { |
||
126 | if ( preg_match('#fuelups/(\d+)/edit#', $fuelup[0], $match) ) { |
||
127 | $fuelup = array( |
||
128 | 'id' => $match[1], |
||
129 | 'usercar_id' => $vehicle->id, |
||
130 | 'fuelup_date' => $fuelup[2][0], |
||
131 | 'miles_last_fuelup' => $fuelup[3][0], |
||
132 | 'amount' => $fuelup[4][0], |
||
133 | ); |
||
134 | |||
135 | $fuelups[ $fuelup['id'] ] = FuelUp::createFromDetail($vehicle, $fuelup); |
||
136 | } |
||
137 | } |
||
138 | |||
139 | // Sort by date DESC |
||
140 | uasort($fuelups, array(FuelUp::class, 'dateCmp')); |
||
141 | |||
142 | return $fuelups; |
||
143 | } |
||
144 | } |
||
145 | |||
146 | return array(); |
||
147 | } |
||
148 | |||
149 | /** |
||
150 | * |
||
151 | */ |
||
152 | public function getAllFuelups( Vehicle $vehicle ) { |
||
153 | $response = $this->_get('car/make/model/2001/username/' . $vehicle->id . '/export'); |
||
154 | if ( $token = $this->extractFormToken($response->body) ) { |
||
155 | $response = $this->_post('exportfuelups', array( |
||
156 | 'data' => array( |
||
157 | '_token' => $token, |
||
158 | 'usercar_id' => $vehicle->id, |
||
159 | ), |
||
160 | )); |
||
161 | if ( $response->code == 200 ) { |
||
162 | $lines = array_filter(preg_split('#[\r\n]+#', $response->body)); |
||
163 | $header = $rows = array(); |
||
164 | foreach ( $lines as $line ) { |
||
165 | $row = array_map('trim', str_getcsv($line)); |
||
166 | if ( !$header ) { |
||
0 ignored issues
–
show
The expression
$header of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using ![]() |
|||
167 | $header = $row; |
||
168 | } |
||
169 | else { |
||
170 | $rows[] = array_combine($header, $row); |
||
171 | } |
||
172 | } |
||
173 | |||
174 | return $rows; |
||
175 | } |
||
176 | } |
||
177 | } |
||
178 | |||
179 | /** |
||
180 | * |
||
181 | */ |
||
182 | public function addFuelUp( $data ) { |
||
183 | $data = $this->_validateFuelUpData($data); |
||
184 | |||
185 | // GET /fuelups/create |
||
186 | $response = $this->_get('https://www.fuelly.com/fuelups/create?usercar_id=' . $data['usercar_id']); |
||
187 | |||
188 | $token = $this->extractFormToken($response->body); |
||
189 | if ( $token ) { |
||
190 | $data['_token'] = $token; |
||
191 | |||
192 | // POST /fuelups/create |
||
193 | $response = $this->_post('https://www.fuelly.com/fuelups', array( |
||
194 | 'data' => $data, |
||
195 | )); |
||
196 | if ( $response->code == 302 ) { |
||
197 | $response = $this->_get(str_replace('https:', 'http:', $response->headers['location'][0])); |
||
198 | |||
199 | // Take new fuelup ID from response and add it |
||
200 | if ( $response->code == 200 ) { |
||
201 | $regex = '#' . preg_quote($this->base, '#') . 'fuelups/(\d+)/edit#'; |
||
202 | if ( preg_match($regex, $response->body, $match) ) { |
||
203 | $response->fuelup_id = $match[1]; |
||
204 | } |
||
205 | } |
||
206 | |||
207 | return $response; |
||
208 | } |
||
209 | } |
||
210 | } |
||
211 | |||
212 | /** |
||
213 | * |
||
214 | */ |
||
215 | public function getVehicle( $id ) { |
||
216 | $vehicles = $this->getVehicles(); |
||
217 | return @$vehicles[$id]; |
||
218 | } |
||
219 | |||
220 | /** |
||
221 | * |
||
222 | */ |
||
223 | public function getVehicles() { |
||
224 | // Must exist, because session must be valid, so we did a `GET /dashboard` |
||
225 | return $this->vehicles; |
||
226 | } |
||
227 | |||
228 | /** |
||
229 | * |
||
230 | */ |
||
231 | protected function extractVehicles( $page_html ) { |
||
232 | $regex = '#<ul class="dashboard-vehicle" data-clickable="([^"]+)">[\w\W]+?</ul>#'; |
||
233 | $vehicles = array(); |
||
234 | if ( preg_match_all($regex, $page_html, $matches) ) { |
||
235 | foreach ( $matches[0] as $i => $car_html ) { |
||
236 | $url = $matches[1][$i]; |
||
237 | |||
238 | preg_match('#/(\d+)$#', $url, $match); |
||
239 | $id = $match[1]; |
||
240 | |||
241 | preg_match('#<h3[^>]*>(.+?)</h3>#', $car_html, $match); |
||
242 | $name = htmlspecialchars_decode($match[1]); |
||
243 | |||
244 | preg_match("#background\-image:\s*url\(([^\)]+)\)#", $car_html, $match); |
||
245 | $image = $this->base . trim($match[1], "/'"); |
||
246 | |||
247 | preg_match("#data-trend='([^']+)'#", $car_html, $match); |
||
248 | $trend = @json_decode($match[1], true) ?: false; |
||
249 | |||
250 | $vehicles[$id] = new Vehicle($this, compact('url', 'id', 'name', 'image', 'trend')); |
||
251 | } |
||
252 | } |
||
253 | |||
254 | return $vehicles; |
||
255 | } |
||
256 | |||
257 | /** |
||
258 | * |
||
259 | */ |
||
260 | public function logIn() { |
||
261 | if ( !$this->auth->mail || !$this->auth->pass ) { |
||
262 | return false; |
||
263 | } |
||
264 | |||
265 | // GET /login |
||
266 | $response = $this->_get('login', array('login' => true)); |
||
267 | |||
268 | if ( $token = $this->extractFormToken($response->body) ) { |
||
269 | // POST /login |
||
270 | $response = $this->_post('login', array( |
||
271 | 'login' => true, |
||
272 | 'cookies' => $response->cookies, |
||
273 | 'data' => array( |
||
274 | '_token' => $token, |
||
275 | 'email' => $this->auth->mail, |
||
276 | 'password' => $this->auth->pass, |
||
277 | ), |
||
278 | )); |
||
279 | $this->auth->session = $response->cookies_by_name['fuelly_session'][0]; |
||
280 | return $this->checkSession(); |
||
281 | } |
||
282 | |||
283 | return false; |
||
284 | } |
||
285 | |||
286 | /** |
||
287 | * |
||
288 | */ |
||
289 | public function checkSession() { |
||
290 | if ( !$this->auth->session ) { |
||
291 | return false; |
||
292 | } |
||
293 | |||
294 | $response = $this->_get('dashboard'); |
||
295 | if ( $response->code == 200 ) { |
||
296 | $regex = '#<a href="' . preg_quote($this->base, '#') . 'driver/([\w\d]+)/edit">Settings</a>#'; |
||
297 | if ( preg_match($regex, $response->body, $match) ) { |
||
298 | $this->username = $match[1]; |
||
299 | |||
300 | // Since we're downloading /dashboard anyway, let's extract our vehicles from it |
||
301 | $this->vehicles = $this->extractVehicles($response->body); |
||
302 | |||
303 | return true; |
||
304 | } |
||
305 | } |
||
306 | |||
307 | return false; |
||
308 | } |
||
309 | |||
310 | /** |
||
311 | * |
||
312 | */ |
||
313 | public function ensureSession() { |
||
314 | if ( !$this->checkSession() ) { |
||
315 | return $this->logIn(); |
||
316 | } |
||
317 | |||
318 | return true; |
||
319 | } |
||
320 | |||
321 | /** |
||
322 | * |
||
323 | */ |
||
324 | protected function extractFormToken( $html ) { |
||
325 | $doc = Node::create($html); |
||
326 | $el = $doc->query('input[name="_token"]'); |
||
327 | return $el['value']; |
||
328 | } |
||
329 | |||
330 | |||
331 | |||
332 | /** |
||
333 | * HTTP GET |
||
334 | */ |
||
335 | public function _get( $uri, $options = array() ) { |
||
336 | return $this->_http($uri, $options + array('method' => 'GET')); |
||
337 | } |
||
338 | |||
339 | /** |
||
340 | * HTTP POST |
||
341 | */ |
||
342 | public function _post( $uri, $options = array() ) { |
||
343 | return $this->_http($uri, $options + array('method' => 'POST')); |
||
344 | } |
||
345 | |||
346 | /** |
||
347 | * HTTP URL |
||
348 | */ |
||
349 | public function _url( $uri, $options = array() ) { |
||
350 | $base = !empty($options['login']) ? $this->loginBase : $this->base; |
||
351 | $url = strpos($uri, '://') ? $uri : $base . $uri; |
||
352 | return $url; |
||
353 | } |
||
354 | |||
355 | /** |
||
356 | * HTTP REQUEST |
||
357 | */ |
||
358 | public function _http( $uri, $options = array() ) { |
||
359 | if ( $this->auth->session ) { |
||
360 | $options['cookies'][] = array('fuelly_session', $this->auth->session); |
||
361 | } |
||
362 | |||
363 | $url = $this->_url($uri, $options); |
||
364 | |||
365 | $log = array(); |
||
366 | $log['req'] = $options['method'] . ' ' . $url; |
||
367 | $this->log[] = &$log; |
||
368 | |||
369 | $_start = microtime(1); |
||
370 | $request = HTTP::create($url, $options); |
||
371 | |||
372 | $response = $request->request(); |
||
373 | $_time = microtime(1) - $_start; |
||
374 | |||
375 | $log['rsp'] = $response->code . ' ' . $response->status; |
||
376 | $log['time'] = $_time; |
||
377 | |||
378 | return $response; |
||
379 | } |
||
380 | |||
381 | } |
||
382 |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.