wikimedia /
mediawiki
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 | * HTTP service client |
||
| 4 | * |
||
| 5 | * This program is free software; you can redistribute it and/or modify |
||
| 6 | * it under the terms of the GNU General Public License as published by |
||
| 7 | * the Free Software Foundation; either version 2 of the License, or |
||
| 8 | * (at your option) any later version. |
||
| 9 | * |
||
| 10 | * This program is distributed in the hope that it will be useful, |
||
| 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
| 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
| 13 | * GNU General Public License for more details. |
||
| 14 | * |
||
| 15 | * You should have received a copy of the GNU General Public License along |
||
| 16 | * with this program; if not, write to the Free Software Foundation, Inc., |
||
| 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||
| 18 | * http://www.gnu.org/copyleft/gpl.html |
||
| 19 | * |
||
| 20 | * @file |
||
| 21 | */ |
||
| 22 | |||
| 23 | /** |
||
| 24 | * Class to handle concurrent HTTP requests |
||
| 25 | * |
||
| 26 | * HTTP request maps are arrays that use the following format: |
||
| 27 | * - method : GET/HEAD/PUT/POST/DELETE |
||
| 28 | * - url : HTTP/HTTPS URL |
||
| 29 | * - query : <query parameter field/value associative array> (uses RFC 3986) |
||
| 30 | * - headers : <header name/value associative array> |
||
| 31 | * - body : source to get the HTTP request body from; |
||
| 32 | * this can simply be a string (always), a resource for |
||
| 33 | * PUT requests, and a field/value array for POST request; |
||
| 34 | * array bodies are encoded as multipart/form-data and strings |
||
| 35 | * use application/x-www-form-urlencoded (headers sent automatically) |
||
| 36 | * - stream : resource to stream the HTTP response body to |
||
| 37 | * - proxy : HTTP proxy to use |
||
| 38 | * - flags : map of boolean flags which supports: |
||
| 39 | * - relayResponseHeaders : write out header via header() |
||
| 40 | * Request maps can use integer index 0 instead of 'method' and 1 instead of 'url'. |
||
| 41 | * |
||
| 42 | * @author Aaron Schulz |
||
| 43 | * @since 1.23 |
||
| 44 | */ |
||
| 45 | class MultiHttpClient { |
||
| 46 | /** @var resource */ |
||
| 47 | protected $multiHandle = null; // curl_multi handle |
||
| 48 | /** @var string|null SSL certificates path */ |
||
| 49 | protected $caBundlePath; |
||
| 50 | /** @var integer */ |
||
| 51 | protected $connTimeout = 10; |
||
| 52 | /** @var integer */ |
||
| 53 | protected $reqTimeout = 300; |
||
| 54 | /** @var bool */ |
||
| 55 | protected $usePipelining = false; |
||
| 56 | /** @var integer */ |
||
| 57 | protected $maxConnsPerHost = 50; |
||
| 58 | /** @var string|null proxy */ |
||
| 59 | protected $proxy; |
||
| 60 | /** @var string */ |
||
| 61 | protected $userAgent = 'wikimedia/multi-http-client v1.0'; |
||
| 62 | |||
| 63 | /** |
||
| 64 | * @param array $options |
||
| 65 | * - connTimeout : default connection timeout (seconds) |
||
| 66 | * - reqTimeout : default request timeout (seconds) |
||
| 67 | * - proxy : HTTP proxy to use |
||
| 68 | * - usePipelining : whether to use HTTP pipelining if possible (for all hosts) |
||
| 69 | * - maxConnsPerHost : maximum number of concurrent connections (per host) |
||
| 70 | * - userAgent : The User-Agent header value to send |
||
| 71 | * @throws Exception |
||
| 72 | */ |
||
| 73 | public function __construct( array $options ) { |
||
| 74 | if ( isset( $options['caBundlePath'] ) ) { |
||
| 75 | $this->caBundlePath = $options['caBundlePath']; |
||
| 76 | if ( !file_exists( $this->caBundlePath ) ) { |
||
| 77 | throw new Exception( "Cannot find CA bundle: " . $this->caBundlePath ); |
||
| 78 | } |
||
| 79 | } |
||
| 80 | static $opts = [ |
||
| 81 | 'connTimeout', 'reqTimeout', 'usePipelining', 'maxConnsPerHost', 'proxy', 'userAgent' |
||
| 82 | ]; |
||
| 83 | foreach ( $opts as $key ) { |
||
| 84 | if ( isset( $options[$key] ) ) { |
||
| 85 | $this->$key = $options[$key]; |
||
| 86 | } |
||
| 87 | } |
||
| 88 | } |
||
| 89 | |||
| 90 | /** |
||
| 91 | * Execute an HTTP(S) request |
||
| 92 | * |
||
| 93 | * This method returns a response map of: |
||
| 94 | * - code : HTTP response code or 0 if there was a serious cURL error |
||
| 95 | * - reason : HTTP response reason (empty if there was a serious cURL error) |
||
| 96 | * - headers : <header name/value associative array> |
||
| 97 | * - body : HTTP response body or resource (if "stream" was set) |
||
| 98 | * - error : Any cURL error string |
||
| 99 | * The map also stores integer-indexed copies of these values. This lets callers do: |
||
| 100 | * @code |
||
| 101 | * list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $http->run( $req ); |
||
| 102 | * @endcode |
||
| 103 | * @param array $req HTTP request array |
||
| 104 | * @param array $opts |
||
| 105 | * - connTimeout : connection timeout per request (seconds) |
||
| 106 | * - reqTimeout : post-connection timeout per request (seconds) |
||
| 107 | * @return array Response array for request |
||
| 108 | */ |
||
| 109 | public function run( array $req, array $opts = [] ) { |
||
| 110 | return $this->runMulti( [ $req ], $opts )[0]['response']; |
||
| 111 | } |
||
| 112 | |||
| 113 | /** |
||
| 114 | * Execute a set of HTTP(S) requests concurrently |
||
| 115 | * |
||
| 116 | * The maps are returned by this method with the 'response' field set to a map of: |
||
| 117 | * - code : HTTP response code or 0 if there was a serious cURL error |
||
| 118 | * - reason : HTTP response reason (empty if there was a serious cURL error) |
||
| 119 | * - headers : <header name/value associative array> |
||
| 120 | * - body : HTTP response body or resource (if "stream" was set) |
||
| 121 | * - error : Any cURL error string |
||
| 122 | * The map also stores integer-indexed copies of these values. This lets callers do: |
||
| 123 | * @code |
||
| 124 | * list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $req['response']; |
||
| 125 | * @endcode |
||
| 126 | * All headers in the 'headers' field are normalized to use lower case names. |
||
| 127 | * This is true for the request headers and the response headers. Integer-indexed |
||
| 128 | * method/URL entries will also be changed to use the corresponding string keys. |
||
| 129 | * |
||
| 130 | * @param array $reqs Map of HTTP request arrays |
||
| 131 | * @param array $opts |
||
| 132 | * - connTimeout : connection timeout per request (seconds) |
||
| 133 | * - reqTimeout : post-connection timeout per request (seconds) |
||
| 134 | * - usePipelining : whether to use HTTP pipelining if possible |
||
| 135 | * - maxConnsPerHost : maximum number of concurrent connections (per host) |
||
| 136 | * @return array $reqs With response array populated for each |
||
| 137 | * @throws Exception |
||
| 138 | */ |
||
| 139 | public function runMulti( array $reqs, array $opts = [] ) { |
||
| 140 | $chm = $this->getCurlMulti(); |
||
| 141 | |||
| 142 | // Normalize $reqs and add all of the required cURL handles... |
||
| 143 | $handles = []; |
||
| 144 | foreach ( $reqs as $index => &$req ) { |
||
| 145 | $req['response'] = [ |
||
| 146 | 'code' => 0, |
||
| 147 | 'reason' => '', |
||
| 148 | 'headers' => [], |
||
| 149 | 'body' => '', |
||
| 150 | 'error' => '' |
||
| 151 | ]; |
||
| 152 | if ( isset( $req[0] ) ) { |
||
| 153 | $req['method'] = $req[0]; // short-form |
||
| 154 | unset( $req[0] ); |
||
| 155 | } |
||
| 156 | if ( isset( $req[1] ) ) { |
||
| 157 | $req['url'] = $req[1]; // short-form |
||
| 158 | unset( $req[1] ); |
||
| 159 | } |
||
| 160 | if ( !isset( $req['method'] ) ) { |
||
| 161 | throw new Exception( "Request has no 'method' field set." ); |
||
| 162 | } elseif ( !isset( $req['url'] ) ) { |
||
| 163 | throw new Exception( "Request has no 'url' field set." ); |
||
| 164 | } |
||
| 165 | $req['query'] = isset( $req['query'] ) ? $req['query'] : []; |
||
| 166 | $headers = []; // normalized headers |
||
| 167 | if ( isset( $req['headers'] ) ) { |
||
| 168 | foreach ( $req['headers'] as $name => $value ) { |
||
| 169 | $headers[strtolower( $name )] = $value; |
||
| 170 | } |
||
| 171 | } |
||
| 172 | $req['headers'] = $headers; |
||
| 173 | if ( !isset( $req['body'] ) ) { |
||
| 174 | $req['body'] = ''; |
||
| 175 | $req['headers']['content-length'] = 0; |
||
| 176 | } |
||
| 177 | $req['flags'] = isset( $req['flags'] ) ? $req['flags'] : []; |
||
| 178 | $handles[$index] = $this->getCurlHandle( $req, $opts ); |
||
| 179 | if ( count( $reqs ) > 1 ) { |
||
| 180 | // https://github.com/guzzle/guzzle/issues/349 |
||
| 181 | curl_setopt( $handles[$index], CURLOPT_FORBID_REUSE, true ); |
||
| 182 | } |
||
| 183 | } |
||
| 184 | unset( $req ); // don't assign over this by accident |
||
| 185 | |||
| 186 | $indexes = array_keys( $reqs ); |
||
| 187 | if ( isset( $opts['usePipelining'] ) ) { |
||
| 188 | curl_multi_setopt( $chm, CURLMOPT_PIPELINING, (int)$opts['usePipelining'] ); |
||
| 189 | } |
||
| 190 | if ( isset( $opts['maxConnsPerHost'] ) ) { |
||
| 191 | // Keep these sockets around as they may be needed later in the request |
||
| 192 | curl_multi_setopt( $chm, CURLMOPT_MAXCONNECTS, (int)$opts['maxConnsPerHost'] ); |
||
| 193 | } |
||
| 194 | |||
| 195 | // @TODO: use a per-host rolling handle window (e.g. CURLMOPT_MAX_HOST_CONNECTIONS) |
||
| 196 | $batches = array_chunk( $indexes, $this->maxConnsPerHost ); |
||
| 197 | $infos = []; |
||
| 198 | |||
| 199 | foreach ( $batches as $batch ) { |
||
| 200 | // Attach all cURL handles for this batch |
||
| 201 | foreach ( $batch as $index ) { |
||
| 202 | curl_multi_add_handle( $chm, $handles[$index] ); |
||
| 203 | } |
||
| 204 | // Execute the cURL handles concurrently... |
||
| 205 | $active = null; // handles still being processed |
||
| 206 | do { |
||
| 207 | // Do any available work... |
||
| 208 | do { |
||
| 209 | $mrc = curl_multi_exec( $chm, $active ); |
||
| 210 | $info = curl_multi_info_read( $chm ); |
||
| 211 | if ( $info !== false ) { |
||
| 212 | $infos[(int)$info['handle']] = $info; |
||
| 213 | } |
||
| 214 | } while ( $mrc == CURLM_CALL_MULTI_PERFORM ); |
||
| 215 | // Wait (if possible) for available work... |
||
| 216 | if ( $active > 0 && $mrc == CURLM_OK ) { |
||
| 217 | if ( curl_multi_select( $chm, 10 ) == -1 ) { |
||
| 218 | // PHP bug 63411; https://curl.haxx.se/libcurl/c/curl_multi_fdset.html |
||
| 219 | usleep( 5000 ); // 5ms |
||
| 220 | } |
||
| 221 | } |
||
| 222 | } while ( $active > 0 && $mrc == CURLM_OK ); |
||
| 223 | } |
||
| 224 | |||
| 225 | // Remove all of the added cURL handles and check for errors... |
||
| 226 | foreach ( $reqs as $index => &$req ) { |
||
| 227 | $ch = $handles[$index]; |
||
| 228 | curl_multi_remove_handle( $chm, $ch ); |
||
| 229 | |||
| 230 | if ( isset( $infos[(int)$ch] ) ) { |
||
| 231 | $info = $infos[(int)$ch]; |
||
| 232 | $errno = $info['result']; |
||
| 233 | if ( $errno !== 0 ) { |
||
| 234 | $req['response']['error'] = "(curl error: $errno)"; |
||
| 235 | if ( function_exists( 'curl_strerror' ) ) { |
||
| 236 | $req['response']['error'] .= " " . curl_strerror( $errno ); |
||
| 237 | } |
||
| 238 | } |
||
| 239 | } else { |
||
| 240 | $req['response']['error'] = "(curl error: no status set)"; |
||
| 241 | } |
||
| 242 | |||
| 243 | // For convenience with the list() operator |
||
| 244 | $req['response'][0] = $req['response']['code']; |
||
| 245 | $req['response'][1] = $req['response']['reason']; |
||
| 246 | $req['response'][2] = $req['response']['headers']; |
||
| 247 | $req['response'][3] = $req['response']['body']; |
||
| 248 | $req['response'][4] = $req['response']['error']; |
||
| 249 | curl_close( $ch ); |
||
| 250 | // Close any string wrapper file handles |
||
| 251 | if ( isset( $req['_closeHandle'] ) ) { |
||
| 252 | fclose( $req['_closeHandle'] ); |
||
| 253 | unset( $req['_closeHandle'] ); |
||
| 254 | } |
||
| 255 | } |
||
| 256 | unset( $req ); // don't assign over this by accident |
||
| 257 | |||
| 258 | // Restore the default settings |
||
| 259 | curl_multi_setopt( $chm, CURLMOPT_PIPELINING, (int)$this->usePipelining ); |
||
| 260 | curl_multi_setopt( $chm, CURLMOPT_MAXCONNECTS, (int)$this->maxConnsPerHost ); |
||
| 261 | |||
| 262 | return $reqs; |
||
| 263 | } |
||
| 264 | |||
| 265 | /** |
||
| 266 | * @param array $req HTTP request map |
||
| 267 | * @param array $opts |
||
| 268 | * - connTimeout : default connection timeout |
||
| 269 | * - reqTimeout : default request timeout |
||
| 270 | * @return resource |
||
| 271 | * @throws Exception |
||
| 272 | */ |
||
| 273 | protected function getCurlHandle( array &$req, array $opts = [] ) { |
||
| 274 | $ch = curl_init(); |
||
| 275 | |||
| 276 | curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, |
||
| 277 | isset( $opts['connTimeout'] ) ? $opts['connTimeout'] : $this->connTimeout ); |
||
| 278 | curl_setopt( $ch, CURLOPT_PROXY, isset( $req['proxy'] ) ? $req['proxy'] : $this->proxy ); |
||
| 279 | curl_setopt( $ch, CURLOPT_TIMEOUT, |
||
| 280 | isset( $opts['reqTimeout'] ) ? $opts['reqTimeout'] : $this->reqTimeout ); |
||
| 281 | curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 1 ); |
||
| 282 | curl_setopt( $ch, CURLOPT_MAXREDIRS, 4 ); |
||
| 283 | curl_setopt( $ch, CURLOPT_HEADER, 0 ); |
||
| 284 | if ( !is_null( $this->caBundlePath ) ) { |
||
| 285 | curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, true ); |
||
| 286 | curl_setopt( $ch, CURLOPT_CAINFO, $this->caBundlePath ); |
||
| 287 | } |
||
| 288 | curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 ); |
||
| 289 | |||
| 290 | $url = $req['url']; |
||
| 291 | $query = http_build_query( $req['query'], '', '&', PHP_QUERY_RFC3986 ); |
||
| 292 | if ( $query != '' ) { |
||
| 293 | $url .= strpos( $req['url'], '?' ) === false ? "?$query" : "&$query"; |
||
| 294 | } |
||
| 295 | curl_setopt( $ch, CURLOPT_URL, $url ); |
||
| 296 | |||
| 297 | curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, $req['method'] ); |
||
| 298 | if ( $req['method'] === 'HEAD' ) { |
||
| 299 | curl_setopt( $ch, CURLOPT_NOBODY, 1 ); |
||
| 300 | } |
||
| 301 | |||
| 302 | if ( $req['method'] === 'PUT' ) { |
||
| 303 | curl_setopt( $ch, CURLOPT_PUT, 1 ); |
||
| 304 | if ( is_resource( $req['body'] ) ) { |
||
| 305 | curl_setopt( $ch, CURLOPT_INFILE, $req['body'] ); |
||
| 306 | if ( isset( $req['headers']['content-length'] ) ) { |
||
| 307 | curl_setopt( $ch, CURLOPT_INFILESIZE, $req['headers']['content-length'] ); |
||
| 308 | } elseif ( isset( $req['headers']['transfer-encoding'] ) && |
||
| 309 | $req['headers']['transfer-encoding'] === 'chunks' |
||
| 310 | ) { |
||
| 311 | curl_setopt( $ch, CURLOPT_UPLOAD, true ); |
||
| 312 | } else { |
||
| 313 | throw new Exception( "Missing 'Content-Length' or 'Transfer-Encoding' header." ); |
||
| 314 | } |
||
| 315 | } elseif ( $req['body'] !== '' ) { |
||
| 316 | $fp = fopen( "php://temp", "wb+" ); |
||
| 317 | fwrite( $fp, $req['body'], strlen( $req['body'] ) ); |
||
| 318 | rewind( $fp ); |
||
| 319 | curl_setopt( $ch, CURLOPT_INFILE, $fp ); |
||
| 320 | curl_setopt( $ch, CURLOPT_INFILESIZE, strlen( $req['body'] ) ); |
||
| 321 | $req['_closeHandle'] = $fp; // remember to close this later |
||
| 322 | } else { |
||
| 323 | curl_setopt( $ch, CURLOPT_INFILESIZE, 0 ); |
||
| 324 | } |
||
| 325 | curl_setopt( $ch, CURLOPT_READFUNCTION, |
||
| 326 | function ( $ch, $fd, $length ) { |
||
| 327 | $data = fread( $fd, $length ); |
||
| 328 | $len = strlen( $data ); |
||
|
0 ignored issues
–
show
|
|||
| 329 | return $data; |
||
| 330 | } |
||
| 331 | ); |
||
| 332 | } elseif ( $req['method'] === 'POST' ) { |
||
| 333 | curl_setopt( $ch, CURLOPT_POST, 1 ); |
||
| 334 | // Don't interpret POST parameters starting with '@' as file uploads, because this |
||
| 335 | // makes it impossible to POST plain values starting with '@' (and causes security |
||
| 336 | // issues potentially exposing the contents of local files). |
||
| 337 | // The PHP manual says this option was introduced in PHP 5.5 defaults to true in PHP 5.6, |
||
| 338 | // but we support lower versions, and the option doesn't exist in HHVM 5.6.99. |
||
| 339 | if ( defined( 'CURLOPT_SAFE_UPLOAD' ) ) { |
||
| 340 | curl_setopt( $ch, CURLOPT_SAFE_UPLOAD, true ); |
||
| 341 | } elseif ( is_array( $req['body'] ) ) { |
||
| 342 | // In PHP 5.2 and later, '@' is interpreted as a file upload if POSTFIELDS |
||
| 343 | // is an array, but not if it's a string. So convert $req['body'] to a string |
||
| 344 | // for safety. |
||
| 345 | $req['body'] = http_build_query( $req['body'] ); |
||
| 346 | } |
||
| 347 | curl_setopt( $ch, CURLOPT_POSTFIELDS, $req['body'] ); |
||
| 348 | } else { |
||
| 349 | if ( is_resource( $req['body'] ) || $req['body'] !== '' ) { |
||
| 350 | throw new Exception( "HTTP body specified for a non PUT/POST request." ); |
||
| 351 | } |
||
| 352 | $req['headers']['content-length'] = 0; |
||
| 353 | } |
||
| 354 | |||
| 355 | if ( !isset( $req['headers']['user-agent'] ) ) { |
||
| 356 | $req['headers']['user-agent'] = $this->userAgent; |
||
| 357 | } |
||
| 358 | |||
| 359 | $headers = []; |
||
| 360 | foreach ( $req['headers'] as $name => $value ) { |
||
| 361 | if ( strpos( $name, ': ' ) ) { |
||
| 362 | throw new Exception( "Headers cannot have ':' in the name." ); |
||
| 363 | } |
||
| 364 | $headers[] = $name . ': ' . trim( $value ); |
||
| 365 | } |
||
| 366 | curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers ); |
||
| 367 | |||
| 368 | curl_setopt( $ch, CURLOPT_HEADERFUNCTION, |
||
| 369 | function ( $ch, $header ) use ( &$req ) { |
||
| 370 | if ( !empty( $req['flags']['relayResponseHeaders'] ) ) { |
||
| 371 | header( $header ); |
||
| 372 | } |
||
| 373 | $length = strlen( $header ); |
||
| 374 | $matches = []; |
||
| 375 | if ( preg_match( "/^(HTTP\/1\.[01]) (\d{3}) (.*)/", $header, $matches ) ) { |
||
| 376 | $req['response']['code'] = (int)$matches[2]; |
||
| 377 | $req['response']['reason'] = trim( $matches[3] ); |
||
| 378 | return $length; |
||
| 379 | } |
||
| 380 | if ( strpos( $header, ":" ) === false ) { |
||
| 381 | return $length; |
||
| 382 | } |
||
| 383 | list( $name, $value ) = explode( ":", $header, 2 ); |
||
| 384 | $req['response']['headers'][strtolower( $name )] = trim( $value ); |
||
| 385 | return $length; |
||
| 386 | } |
||
| 387 | ); |
||
| 388 | |||
| 389 | if ( isset( $req['stream'] ) ) { |
||
| 390 | // Don't just use CURLOPT_FILE as that might give: |
||
| 391 | // curl_setopt(): cannot represent a stream of type Output as a STDIO FILE* |
||
| 392 | // The callback here handles both normal files and php://temp handles. |
||
| 393 | curl_setopt( $ch, CURLOPT_WRITEFUNCTION, |
||
| 394 | function ( $ch, $data ) use ( &$req ) { |
||
| 395 | return fwrite( $req['stream'], $data ); |
||
| 396 | } |
||
| 397 | ); |
||
| 398 | } else { |
||
| 399 | curl_setopt( $ch, CURLOPT_WRITEFUNCTION, |
||
| 400 | function ( $ch, $data ) use ( &$req ) { |
||
| 401 | $req['response']['body'] .= $data; |
||
| 402 | return strlen( $data ); |
||
| 403 | } |
||
| 404 | ); |
||
| 405 | } |
||
| 406 | |||
| 407 | return $ch; |
||
| 408 | } |
||
| 409 | |||
| 410 | /** |
||
| 411 | * @return resource |
||
| 412 | */ |
||
| 413 | protected function getCurlMulti() { |
||
| 414 | if ( !$this->multiHandle ) { |
||
| 415 | $cmh = curl_multi_init(); |
||
| 416 | curl_multi_setopt( $cmh, CURLMOPT_PIPELINING, (int)$this->usePipelining ); |
||
| 417 | curl_multi_setopt( $cmh, CURLMOPT_MAXCONNECTS, (int)$this->maxConnsPerHost ); |
||
| 418 | $this->multiHandle = $cmh; |
||
| 419 | } |
||
| 420 | return $this->multiHandle; |
||
| 421 | } |
||
| 422 | |||
| 423 | function __destruct() { |
||
| 424 | if ( $this->multiHandle ) { |
||
| 425 | curl_multi_close( $this->multiHandle ); |
||
| 426 | } |
||
| 427 | } |
||
| 428 | } |
||
| 429 |
This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.
Both the
$myVarassignment in line 1 and the$higherassignment in line 2 are dead. The first because$myVaris never used and the second because$higheris always overwritten for every possible time line.