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 | namespace PHPDaemon\Clients\HTTP; |
||
3 | |||
4 | use PHPDaemon\Clients\HTTP\UploadFile; |
||
5 | use PHPDaemon\Core\Daemon; |
||
6 | use PHPDaemon\HTTPRequest\Generic; |
||
7 | use PHPDaemon\Network\ClientConnection; |
||
8 | |||
9 | /** |
||
10 | * @package NetworkClients |
||
11 | * @subpackage HTTPClient |
||
12 | * @author Vasily Zorin <[email protected]> |
||
13 | */ |
||
14 | class Connection extends ClientConnection |
||
15 | { |
||
16 | /** |
||
17 | * State: headers |
||
18 | */ |
||
19 | const STATE_HEADERS = 1; |
||
20 | |||
21 | /** |
||
22 | * State: body |
||
23 | */ |
||
24 | const STATE_BODY = 2; |
||
25 | |||
26 | /** |
||
27 | * @var array Associative array of headers |
||
28 | */ |
||
29 | public $headers = []; |
||
30 | |||
31 | /** |
||
32 | * @var integer Content length |
||
33 | */ |
||
34 | public $contentLength = -1; |
||
35 | |||
36 | /** |
||
37 | * @var string Contains response body |
||
38 | */ |
||
39 | public $body = ''; |
||
40 | |||
41 | /** |
||
42 | * @var string End of line |
||
43 | */ |
||
44 | protected $EOL = "\r\n"; |
||
45 | |||
46 | /** |
||
47 | * @var array Associative array of Cookies |
||
48 | */ |
||
49 | public $cookie = []; |
||
50 | |||
51 | /** |
||
52 | * @var integer Size of current chunk |
||
53 | */ |
||
54 | protected $curChunkSize; |
||
55 | |||
56 | /** |
||
57 | * @var string |
||
58 | */ |
||
59 | protected $curChunk; |
||
60 | |||
61 | /** |
||
62 | * @var boolean |
||
63 | */ |
||
64 | public $chunked = false; |
||
65 | |||
66 | /** |
||
67 | * @var callback |
||
68 | */ |
||
69 | public $chunkcb; |
||
70 | |||
71 | /** |
||
72 | * @var integer |
||
73 | */ |
||
74 | public $protocolError; |
||
75 | |||
76 | /** |
||
77 | * @var integer |
||
78 | */ |
||
79 | public $responseCode = 0; |
||
80 | |||
81 | /** |
||
82 | * @var string Last requested URL |
||
83 | */ |
||
84 | public $lastURL; |
||
85 | |||
86 | /** |
||
87 | * @var array Raw headers array |
||
88 | */ |
||
89 | public $rawHeaders = null; |
||
90 | |||
91 | public $contentType; |
||
92 | |||
93 | public $charset; |
||
94 | |||
95 | public $eofTerminated = false; |
||
96 | |||
97 | /** |
||
98 | * @var \SplStack |
||
99 | */ |
||
100 | protected $requests; |
||
101 | |||
102 | /** |
||
103 | * @var string |
||
104 | */ |
||
105 | public $reqType; |
||
106 | |||
107 | /** |
||
108 | * Constructor |
||
109 | */ |
||
110 | protected function init() |
||
111 | { |
||
112 | $this->requests = new \SplStack; |
||
113 | } |
||
114 | |||
115 | /** |
||
116 | * Send request headers |
||
117 | * @param $type |
||
118 | * @param $url |
||
119 | * @param &$params |
||
120 | * @return void |
||
121 | */ |
||
122 | protected function sendRequestHeaders($type, $url, &$params) |
||
123 | { |
||
124 | if (!is_array($params)) { |
||
125 | $params = ['resultcb' => $params]; |
||
126 | } |
||
127 | if (!isset($params['uri']) || !isset($params['host'])) { |
||
128 | $prepared = Pool::parseUrl($url); |
||
129 | if (!$prepared) { |
||
130 | if (isset($params['resultcb'])) { |
||
131 | $params['resultcb'](false); |
||
132 | } |
||
133 | return; |
||
134 | } |
||
135 | list($params['host'], $params['uri']) = $prepared; |
||
136 | } |
||
137 | if ($params['uri'] === '') { |
||
138 | $params['uri'] = '/'; |
||
139 | } |
||
140 | $this->lastURL = 'http://' . $params['host'] . $params['uri']; |
||
141 | if (!isset($params['version'])) { |
||
142 | $params['version'] = '1.1'; |
||
143 | } |
||
144 | $this->writeln($type . ' ' . $params['uri'] . ' HTTP/' . $params['version']); |
||
145 | if (isset($params['proxy'])) { |
||
146 | if (isset($params['proxy']['auth'])) { |
||
147 | $this->writeln('Proxy-Authorization: basic ' . base64_encode($params['proxy']['auth']['username'] . ':' . $params['proxy']['auth']['password'])); |
||
148 | } |
||
149 | } |
||
150 | $this->writeln('Host: ' . $params['host']); |
||
151 | if ($this->pool->config->expose->value && !isset($params['headers']['User-Agent'])) { |
||
152 | $this->writeln('User-Agent: phpDaemon/' . Daemon::$version); |
||
153 | } |
||
154 | if (isset($params['cookie']) && sizeof($params['cookie'])) { |
||
155 | $this->writeln('Cookie: ' . http_build_query($params['cookie'], '', '; ')); |
||
156 | } |
||
157 | if (isset($params['contentType'])) { |
||
158 | if (!isset($params['headers'])) { |
||
159 | $params['headers'] = []; |
||
160 | } |
||
161 | $params['headers']['Content-Type'] = $params['contentType']; |
||
162 | } |
||
163 | if (isset($params['headers'])) { |
||
164 | $this->customRequestHeaders($params['headers']); |
||
165 | } |
||
166 | if (isset($params['rawHeaders']) && $params['rawHeaders']) { |
||
167 | $this->rawHeaders = []; |
||
168 | } |
||
169 | if (isset($params['chunkcb']) && is_callable($params['chunkcb'])) { |
||
170 | $this->chunkcb = $params['chunkcb']; |
||
171 | } |
||
172 | $this->writeln(''); |
||
173 | $this->requests->push($type); |
||
174 | $this->onResponse->push($params['resultcb']); |
||
175 | $this->checkFree(); |
||
176 | } |
||
177 | |||
178 | /** |
||
179 | * Perform a HEAD request |
||
180 | * @param string $url |
||
181 | * @param array $params |
||
0 ignored issues
–
show
|
|||
182 | */ |
||
183 | public function head($url, $params = null) |
||
184 | { |
||
185 | $this->sendRequestHeaders('HEAD', $url, $params); |
||
186 | } |
||
187 | |||
188 | /** |
||
189 | * Perform a GET request |
||
190 | * @param string $url |
||
191 | * @param array $params |
||
0 ignored issues
–
show
Should the type for parameter
$params not be array|null ?
This check looks for It makes a suggestion as to what type it considers more descriptive. Most often this is a case of a parameter that can be null in addition to its declared types. ![]() |
|||
192 | */ |
||
193 | public function get($url, $params = null) |
||
194 | { |
||
195 | $this->sendRequestHeaders('GET', $url, $params); |
||
196 | } |
||
197 | |||
198 | /** |
||
199 | * @param array $headers |
||
200 | */ |
||
201 | protected function customRequestHeaders($headers) |
||
202 | { |
||
203 | foreach ($headers as $key => $item) { |
||
204 | if (is_numeric($key)) { |
||
205 | if (is_string($item)) { |
||
206 | $this->writeln($item); |
||
207 | } elseif (is_array($item)) { |
||
208 | $this->writeln($item[0] . ': ' . $item[1]); // @TODO: prevent injections? |
||
209 | } |
||
210 | } else { |
||
211 | $this->writeln($key . ': ' . $item); |
||
212 | } |
||
213 | } |
||
214 | } |
||
215 | |||
216 | /** |
||
217 | * Perform a POST request |
||
218 | * @param string $url |
||
219 | * @param array $data |
||
220 | * @param array $params |
||
0 ignored issues
–
show
Should the type for parameter
$params not be array|null ?
This check looks for It makes a suggestion as to what type it considers more descriptive. Most often this is a case of a parameter that can be null in addition to its declared types. ![]() |
|||
221 | */ |
||
222 | public function post($url, $data = [], $params = null) |
||
223 | { |
||
224 | if (!is_string($data)) { |
||
225 | foreach ($data as $val) { |
||
226 | if ($val instanceof UploadFile) { |
||
0 ignored issues
–
show
The class
PHPDaemon\Clients\HTTP\UploadFile does not exist. Did you forget a USE statement, or did you not list all dependencies?
This error could be the result of: 1. Missing dependenciesPHP Analyzer uses your Are you sure this class is defined by one of your dependencies, or did you maybe
not list a dependency in either the 2. Missing use statementPHP does not complain about undefined classes in if ($x instanceof DoesNotExist) {
// Do something.
}
If you have not tested against this specific condition, such errors might go unnoticed. ![]() |
|||
227 | $params['contentType'] = 'multipart/form-data'; |
||
228 | } |
||
229 | } |
||
230 | } |
||
231 | if (!isset($params['contentType'])) { |
||
232 | $params['contentType'] = 'application/x-www-form-urlencoded'; |
||
233 | } |
||
234 | |||
235 | if ($params['contentType'] === 'application/x-www-form-urlencoded') { |
||
236 | $body = http_build_query($data, '', '&', PHP_QUERY_RFC3986); |
||
237 | } elseif ($params['contentType'] === 'application/x-json' || $params['contentType'] === 'application/json') { |
||
238 | $body = json_encode($data); |
||
239 | } else { |
||
240 | $body = $data; |
||
241 | } |
||
242 | |||
243 | if (!isset($params['headers'])) { |
||
244 | $params['headers'] = []; |
||
245 | } |
||
246 | $params['headers']['Content-Length'] = mb_orig_strlen($body); |
||
0 ignored issues
–
show
It seems like
$body defined by $data on line 240 can also be of type array ; however, mb_orig_strlen() does only seem to accept string , maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue. ![]() |
|||
247 | $this->sendRequestHeaders('POST', $url, $params); |
||
248 | $this->write($body); |
||
0 ignored issues
–
show
It seems like
$body defined by $data on line 240 can also be of type array ; however, PHPDaemon\Network\Connection::write() does only seem to accept string , maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue. ![]() |
|||
249 | $this->writeln(''); |
||
250 | } |
||
251 | |||
252 | /** |
||
253 | * Get body |
||
254 | * @return string |
||
255 | */ |
||
256 | public function getBody() |
||
257 | { |
||
258 | return $this->body; |
||
259 | } |
||
260 | |||
261 | /** |
||
262 | * Get headers |
||
263 | * @return array |
||
264 | */ |
||
265 | public function getHeaders() |
||
266 | { |
||
267 | return $this->headers; |
||
268 | } |
||
269 | |||
270 | /** |
||
271 | * Get header |
||
272 | * @param string $name Header name |
||
273 | * @return string |
||
274 | */ |
||
275 | public function getHeader($name) |
||
276 | { |
||
277 | $k = 'HTTP_' . strtoupper(strtr($name, Generic::$htr)); |
||
278 | return isset($this->headers[$k]) ? $this->headers[$k] : null; |
||
279 | } |
||
280 | |||
281 | /** |
||
282 | * Called when new data received |
||
283 | */ |
||
284 | public function onRead() |
||
285 | { |
||
286 | if ($this->state === self::STATE_BODY) { |
||
287 | goto body; |
||
288 | } |
||
289 | if ($this->reqType === null) { |
||
290 | if ($this->requests->isEmpty()) { |
||
291 | $this->finish(); |
||
292 | return; |
||
293 | } |
||
294 | $this->reqType = $this->requests->shift(); |
||
295 | } |
||
296 | while (($line = $this->readLine()) !== null) { |
||
297 | if ($line !== '') { |
||
298 | if ($this->rawHeaders !== null) { |
||
299 | $this->rawHeaders[] = $line; |
||
300 | } |
||
301 | } else { |
||
302 | if (isset($this->headers['HTTP_CONTENT_LENGTH'])) { |
||
303 | $this->contentLength = (int)$this->headers['HTTP_CONTENT_LENGTH']; |
||
304 | } else { |
||
305 | $this->contentLength = -1; |
||
306 | } |
||
307 | View Code Duplication | if (isset($this->headers['HTTP_TRANSFER_ENCODING'])) { |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
308 | $e = explode(', ', strtolower($this->headers['HTTP_TRANSFER_ENCODING'])); |
||
309 | $this->chunked = in_array('chunked', $e, true); |
||
310 | } else { |
||
311 | $this->chunked = false; |
||
312 | } |
||
313 | View Code Duplication | if (isset($this->headers['HTTP_CONNECTION'])) { |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
314 | $e = explode(', ', strtolower($this->headers['HTTP_CONNECTION'])); |
||
315 | $this->keepalive = in_array('keep-alive', $e, true); |
||
316 | } |
||
317 | if (isset($this->headers['HTTP_CONTENT_TYPE'])) { |
||
318 | parse_str('type=' . strtr($this->headers['HTTP_CONTENT_TYPE'], [';' => '&', ' ' => '']), $p); |
||
319 | $this->contentType = $p['type']; |
||
320 | if (isset($p['charset'])) { |
||
321 | $this->charset = strtolower($p['charset']); |
||
322 | } |
||
323 | } |
||
324 | if ($this->contentLength === -1 && !$this->chunked && !$this->keepalive) { |
||
325 | $this->eofTerminated = true; |
||
326 | } |
||
327 | if ($this->reqType === 'HEAD') { |
||
328 | $this->requestFinished(); |
||
329 | } else { |
||
330 | $this->state = self::STATE_BODY; |
||
331 | } |
||
332 | break; |
||
333 | } |
||
334 | if ($this->state === self::STATE_ROOT) { |
||
335 | $this->headers['STATUS'] = $line; |
||
336 | $e = explode(' ', $this->headers['STATUS']); |
||
337 | $this->responseCode = isset($e[1]) ? (int)$e[1] : 0; |
||
338 | $this->state = self::STATE_HEADERS; |
||
339 | } elseif ($this->state === self::STATE_HEADERS) { |
||
340 | $e = explode(': ', $line); |
||
341 | |||
342 | if (isset($e[1])) { |
||
343 | $k = 'HTTP_' . strtoupper(strtr($e[0], Generic::$htr)); |
||
344 | if ($k === 'HTTP_SET_COOKIE') { |
||
345 | parse_str(strtr($e[1], [';' => '&', ' ' => '']), $p); |
||
346 | if (sizeof($p)) { |
||
347 | $this->cookie[$k = key($p)] =& $p; |
||
348 | $p['value'] = $p[$k]; |
||
349 | unset($p[$k], $p); |
||
350 | } |
||
351 | } |
||
352 | if (isset($this->headers[$k])) { |
||
353 | if (is_array($this->headers[$k])) { |
||
354 | $this->headers[$k][] = $e[1]; |
||
355 | } else { |
||
356 | $this->headers[$k] = [$this->headers[$k], $e[1]]; |
||
357 | } |
||
358 | } else { |
||
359 | $this->headers[$k] = $e[1]; |
||
360 | } |
||
361 | } |
||
362 | } |
||
363 | } |
||
364 | if ($this->state !== self::STATE_BODY) { |
||
365 | return; // not enough data yet |
||
366 | } |
||
367 | body: |
||
368 | if ($this->eofTerminated) { |
||
369 | $body = $this->readUnlimited(); |
||
370 | if ($this->chunkcb) { |
||
371 | $func = $this->chunkcb; |
||
372 | $func($body); |
||
373 | } |
||
374 | $this->body .= $body; |
||
375 | return; |
||
376 | } |
||
377 | if ($this->chunked) { |
||
378 | chunk: |
||
379 | if ($this->curChunkSize === null) { // outside of chunk |
||
380 | $l = $this->readLine(); |
||
381 | if ($l === '') { // skip empty line |
||
382 | goto chunk; |
||
383 | } |
||
384 | if ($l === null) { |
||
385 | return; // not enough data yet |
||
386 | } |
||
387 | if (!ctype_xdigit($l)) { |
||
388 | $this->protocolError = __LINE__; |
||
389 | $this->finish(); // protocol error |
||
390 | return; |
||
391 | } |
||
392 | $this->curChunkSize = hexdec($l); |
||
0 ignored issues
–
show
It seems like
hexdec($l) can also be of type double . However, the property $curChunkSize is declared as type integer . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||
393 | } |
||
394 | if ($this->curChunkSize !== null) { |
||
395 | if ($this->curChunkSize === 0) { |
||
396 | if ($this->readLine() === '') { |
||
397 | $this->requestFinished(); |
||
398 | return; |
||
399 | } else { // protocol error |
||
400 | $this->protocolError = __LINE__; |
||
401 | $this->finish(); |
||
402 | return; |
||
403 | } |
||
404 | } |
||
405 | $n = $this->curChunkSize - mb_orig_strlen($this->curChunk); |
||
406 | $this->curChunk .= $this->read($n); |
||
407 | if ($this->curChunkSize <= mb_orig_strlen($this->curChunk)) { |
||
408 | if ($this->chunkcb) { |
||
409 | $func = $this->chunkcb; |
||
410 | $func($this->curChunk); |
||
411 | } |
||
412 | $this->body .= $this->curChunk; |
||
413 | $this->curChunkSize = null; |
||
414 | $this->curChunk = ''; |
||
415 | goto chunk; |
||
416 | } |
||
417 | } |
||
418 | } else { |
||
419 | $body = $this->read($this->contentLength - mb_orig_strlen($this->body)); |
||
420 | if ($this->chunkcb) { |
||
421 | $func = $this->chunkcb; |
||
422 | $func($body); |
||
423 | } |
||
424 | $this->body .= $body; |
||
425 | if (($this->contentLength !== -1) && (mb_orig_strlen($this->body) >= $this->contentLength)) { |
||
426 | $this->requestFinished(); |
||
427 | } |
||
428 | } |
||
429 | } |
||
430 | |||
431 | /** |
||
432 | * Called when connection finishes |
||
433 | */ |
||
434 | public function onFinish() |
||
435 | { |
||
436 | if ($this->eofTerminated) { |
||
437 | $this->requestFinished(); |
||
438 | $this->onResponse->executeAll($this, false); |
||
439 | parent::onFinish(); |
||
440 | return; |
||
441 | } |
||
442 | if ($this->protocolError) { |
||
443 | $this->onResponse->executeAll($this, false); |
||
444 | } else { |
||
445 | if (($this->state !== self::STATE_ROOT) && !$this->onResponse->isEmpty()) { |
||
446 | $this->requestFinished(); |
||
447 | } |
||
448 | } |
||
449 | parent::onFinish(); |
||
450 | } |
||
451 | |||
452 | /** |
||
453 | * Called when request is finished |
||
454 | */ |
||
455 | protected function requestFinished() |
||
456 | { |
||
457 | $this->onResponse->executeOne($this, true); |
||
458 | $this->state = self::STATE_ROOT; |
||
459 | $this->contentLength = -1; |
||
460 | $this->curChunkSize = null; |
||
461 | $this->chunked = false; |
||
462 | $this->eofTerminated = false; |
||
463 | $this->headers = []; |
||
464 | $this->rawHeaders = null; |
||
0 ignored issues
–
show
It seems like
null of type null is incompatible with the declared type array of property $rawHeaders .
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property. Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.. ![]() |
|||
465 | $this->contentType = null; |
||
466 | $this->charset = null; |
||
467 | $this->body = ''; |
||
468 | $this->responseCode = 0; |
||
469 | $this->reqType = null; |
||
470 | if (!$this->keepalive) { |
||
471 | $this->finish(); |
||
472 | } |
||
473 | $this->checkFree(); |
||
474 | } |
||
475 | } |
||
476 |
This check looks for
@param
annotations where the type inferred by our type inference engine differs from the declared type.It makes a suggestion as to what type it considers more descriptive.
Most often this is a case of a parameter that can be null in addition to its declared types.