rudiedirkx /
fuelly
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 ) { |
||
|
0 ignored issues
–
show
|
|||
| 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
The expression
$missing of type string[] 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 Loading history...
|
|||
| 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 Loading history...
|
|||
| 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 |
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.