Completed
Push — master ( 85e358...cf3219 )
by Rakesh
04:24 queued 10s
created

Zend_Http_Client_Adapter_Curl::write()   F

Complexity

Conditions 40
Paths 13486

Size

Total Lines 201
Code Lines 114

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 40
eloc 114
c 1
b 0
f 1
nc 13486
nop 5
dl 0
loc 201
rs 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * Zend Framework
5
 *
6
 * LICENSE
7
 *
8
 * This source file is subject to the new BSD license that is bundled
9
 * with this package in the file LICENSE.txt.
10
 * It is also available through the world-wide-web at this URL:
11
 * http://framework.zend.com/license/new-bsd
12
 * If you did not receive a copy of the license and are unable to
13
 * obtain it through the world-wide-web, please send an email
14
 * to [email protected] so we can send you a copy immediately.
15
 *
16
 * @category   Zend
17
 * @package    Zend_Http
18
 * @subpackage Client_Adapter
19
 * @version    $Id$
20
 * @copyright  Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com)
21
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
22
 */
23
24
/**
25
 * @see Zend_Uri_Http
26
 */
27
require_once 'Zend/Uri/Http.php';
28
29
/**
30
 * @see Zend_Http_Client_Adapter_Interface
31
 */
32
require_once 'Zend/Http/Client/Adapter/Interface.php';
33
/**
34
 * @see Zend_Http_Client_Adapter_Stream
35
 */
36
require_once 'Zend/Http/Client/Adapter/Stream.php';
37
38
/**
39
 * An adapter class for Zend_Http_Client based on the curl extension.
40
 * Curl requires libcurl. See for full requirements the PHP manual: http://php.net/curl
41
 *
42
 * @category   Zend
43
 * @package    Zend_Http
44
 * @subpackage Client_Adapter
45
 * @copyright  Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com)
46
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
47
 */
48
class Zend_Http_Client_Adapter_Curl implements Zend_Http_Client_Adapter_Interface, Zend_Http_Client_Adapter_Stream
49
{
50
    /**
51
     * Parameters array
52
     *
53
     * @var array
54
     */
55
    protected $_config = array();
56
57
    /**
58
     * What host/port are we connected to?
59
     *
60
     * @var array
61
     */
62
    protected $_connected_to = array(null, null);
63
64
    /**
65
     * The curl session handle
66
     *
67
     * @var resource|null
68
     */
69
    protected $_curl = null;
70
71
    /**
72
     * List of cURL options that should never be overwritten
73
     *
74
     * @var array
75
     */
76
    protected $_invalidOverwritableCurlOptions;
77
78
    /**
79
     * Response gotten from server
80
     *
81
     * @var string
82
     */
83
    protected $_response = null;
84
85
    /**
86
     * Stream for storing output
87
     *
88
     * @var resource
89
     */
90
    protected $out_stream;
91
92
    /**
93
     * Adapter constructor
94
     *
95
     * Config is set using setConfig()
96
     *
97
     * @return void
98
     * @throws Zend_Http_Client_Adapter_Exception
99
     */
100
    public function __construct()
101
    {
102
        if (!extension_loaded('curl')) {
103
            require_once 'Zend/Http/Client/Adapter/Exception.php';
104
            throw new Zend_Http_Client_Adapter_Exception('cURL extension has to be loaded to use this Zend_Http_Client adapter.');
105
        }
106
        $this->_invalidOverwritableCurlOptions = array(
107
            CURLOPT_HTTPGET,
108
            CURLOPT_POST,
109
            CURLOPT_PUT,
110
            CURLOPT_CUSTOMREQUEST,
111
            CURLOPT_HEADER,
112
            CURLOPT_RETURNTRANSFER,
113
            CURLOPT_HTTPHEADER,
114
            CURLOPT_POSTFIELDS,
115
            CURLOPT_INFILE,
116
            CURLOPT_INFILESIZE,
117
            CURLOPT_PORT,
118
            CURLOPT_MAXREDIRS,
119
            CURLOPT_CONNECTTIMEOUT,
120
            CURL_HTTP_VERSION_1_1,
121
            CURL_HTTP_VERSION_1_0,
122
        );
123
    }
124
125
    /**
126
     * Set the configuration array for the adapter
127
     *
128
     * @throws Zend_Http_Client_Adapter_Exception
129
     * @param  Zend_Config | array $config
130
     * @return Zend_Http_Client_Adapter_Curl
131
     */
132
    public function setConfig($config = array())
133
    {
134
        if ($config instanceof Zend_Config) {
0 ignored issues
show
Bug introduced by
The type Zend_Config was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
135
            $config = $config->toArray();
136
137
        } elseif (! is_array($config)) {
0 ignored issues
show
introduced by
The condition is_array($config) is always true.
Loading history...
138
            require_once 'Zend/Http/Client/Adapter/Exception.php';
139
            throw new Zend_Http_Client_Adapter_Exception(
140
                'Array or Zend_Config object expected, got ' . gettype($config)
141
            );
142
        }
143
144
        if(isset($config['proxy_user']) && isset($config['proxy_pass'])) {
145
            $this->setCurlOption(CURLOPT_PROXYUSERPWD, $config['proxy_user'].":".$config['proxy_pass']);
146
            unset($config['proxy_user'], $config['proxy_pass']);
147
        }
148
149
        foreach ($config as $k => $v) {
150
            $option = strtolower($k);
151
            switch($option) {
152
                case 'proxy_host':
153
                    $this->setCurlOption(CURLOPT_PROXY, $v);
154
                    break;
155
                case 'proxy_port':
156
                    $this->setCurlOption(CURLOPT_PROXYPORT, $v);
157
                    break;
158
                default:
159
                    $this->_config[$option] = $v;
160
                    break;
161
            }
162
        }
163
164
        return $this;
165
    }
166
167
    /**
168
      * Retrieve the array of all configuration options
169
      *
170
      * @return array
171
      */
172
     public function getConfig()
173
     {
174
         return $this->_config;
175
     }
176
177
    /**
178
     * Direct setter for cURL adapter related options.
179
     *
180
     * @param  string|int $option
181
     * @param  mixed $value
182
     * @return Zend_Http_Adapter_Curl
0 ignored issues
show
Bug introduced by
The type Zend_Http_Adapter_Curl was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
183
     */
184
    public function setCurlOption($option, $value)
185
    {
186
        if (!isset($this->_config['curloptions'])) {
187
            $this->_config['curloptions'] = array();
188
        }
189
        $this->_config['curloptions'][$option] = $value;
190
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type Zend_Http_Client_Adapter_Curl which is incompatible with the documented return type Zend_Http_Adapter_Curl.
Loading history...
191
    }
192
193
    /**
194
     * Initialize curl
195
     *
196
     * @param  string  $host
197
     * @param  int     $port
198
     * @param  boolean $secure
199
     * @return void
200
     * @throws Zend_Http_Client_Adapter_Exception if unable to connect
201
     */
202
    public function connect($host, $port = 80, $secure = false)
203
    {
204
        // If we're already connected, disconnect first
205
        if ($this->_curl) {
206
            $this->close();
207
        }
208
209
        // If we are connected to a different server or port, disconnect first
210
        if ($this->_curl
211
            && is_array($this->_connected_to)
212
            && ($this->_connected_to[0] != $host
213
            || $this->_connected_to[1] != $port)
214
        ) {
215
            $this->close();
216
        }
217
218
        // Do the actual connection
219
        $this->_curl = curl_init();
0 ignored issues
show
Documentation Bug introduced by
It seems like curl_init() can also be of type false. However, the property $_curl is declared as type null|resource. 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 $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

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;
}
Loading history...
220
        if ($port != 80) {
221
            curl_setopt($this->_curl, CURLOPT_PORT, intval($port));
0 ignored issues
show
Bug introduced by
It seems like $this->_curl can also be of type false; however, parameter $ch of curl_setopt() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

221
            curl_setopt(/** @scrutinizer ignore-type */ $this->_curl, CURLOPT_PORT, intval($port));
Loading history...
222
        }
223
224
        // Set timeout
225
        if (defined('CURLOPT_CONNECTTIMEOUT_MS')) {
226
            curl_setopt($this->_curl, CURLOPT_CONNECTTIMEOUT_MS, $this->_config['timeout'] * 1000);
227
        } else {
228
            curl_setopt($this->_curl, CURLOPT_CONNECTTIMEOUT, $this->_config['timeout']);
229
        }
230
231
        if (defined('CURLOPT_TIMEOUT_MS')) {
232
            curl_setopt($this->_curl, CURLOPT_TIMEOUT_MS, $this->_config['timeout'] * 1000);
233
        } else {
234
            curl_setopt($this->_curl, CURLOPT_TIMEOUT, $this->_config['timeout']);
235
        }
236
237
        // Set Max redirects
238
        curl_setopt($this->_curl, CURLOPT_MAXREDIRS, $this->_config['maxredirects']);
239
240
        if (!$this->_curl) {
241
            $this->close();
242
243
            require_once 'Zend/Http/Client/Adapter/Exception.php';
244
            throw new Zend_Http_Client_Adapter_Exception('Unable to Connect to ' .  $host . ':' . $port);
245
        }
246
247
        if ($secure !== false) {
248
            // Behave the same like Zend_Http_Adapter_Socket on SSL options.
249
            if (isset($this->_config['sslcert'])) {
250
                curl_setopt($this->_curl, CURLOPT_SSLCERT, $this->_config['sslcert']);
251
            }
252
            if (isset($this->_config['sslpassphrase'])) {
253
                curl_setopt($this->_curl, CURLOPT_SSLCERTPASSWD, $this->_config['sslpassphrase']);
254
            }
255
        }
256
257
        // Update connected_to
258
        $this->_connected_to = array($host, $port);
259
    }
260
261
    /**
262
     * Send request to the remote server
263
     *
264
     * @param  string        $method
265
     * @param  Zend_Uri_Http $uri
266
     * @param  float         $http_ver
267
     * @param  array         $headers
268
     * @param  string        $body
269
     * @return string        $request
270
     * @throws Zend_Http_Client_Adapter_Exception If connection fails, connected to wrong host, no PUT file defined, unsupported method, or unsupported cURL option
271
     */
272
    public function write($method, $uri, $httpVersion = 1.1, $headers = array(), $body = '')
273
    {
274
        // Make sure we're properly connected
275
        if (!$this->_curl) {
276
            require_once 'Zend/Http/Client/Adapter/Exception.php';
277
            throw new Zend_Http_Client_Adapter_Exception("Trying to write but we are not connected");
278
        }
279
280
        if ($this->_connected_to[0] != $uri->getHost() || $this->_connected_to[1] != $uri->getPort()) {
281
            require_once 'Zend/Http/Client/Adapter/Exception.php';
282
            throw new Zend_Http_Client_Adapter_Exception("Trying to write but we are connected to the wrong host");
283
        }
284
285
        // set URL
286
        curl_setopt($this->_curl, CURLOPT_URL, $uri->__toString());
287
288
        // ensure correct curl call
289
        $curlValue = true;
290
        switch ($method) {
291
            case Zend_Http_Client::GET:
292
                $curlMethod = CURLOPT_HTTPGET;
293
                break;
294
295
            case Zend_Http_Client::POST:
296
                $curlMethod = CURLOPT_POST;
297
                break;
298
299
            case Zend_Http_Client::PUT:
300
                // There are two different types of PUT request, either a Raw Data string has been set
301
                // or CURLOPT_INFILE and CURLOPT_INFILESIZE are used.
302
                if(is_resource($body)) {
0 ignored issues
show
introduced by
The condition is_resource($body) is always false.
Loading history...
303
                    $this->_config['curloptions'][CURLOPT_INFILE] = $body;
304
                }
305
                if (isset($this->_config['curloptions'][CURLOPT_INFILE])) {
306
                    // Now we will probably already have Content-Length set, so that we have to delete it
307
                    // from $headers at this point:
308
                    foreach ($headers AS $k => $header) {
309
                        if (preg_match('/Content-Length:\s*(\d+)/i', $header, $m)) {
310
                            if(is_resource($body)) {
311
                                $this->_config['curloptions'][CURLOPT_INFILESIZE] = (int)$m[1];
312
                            }
313
                            unset($headers[$k]);
314
                        }
315
                    }
316
317
                    if (!isset($this->_config['curloptions'][CURLOPT_INFILESIZE])) {
318
                        require_once 'Zend/Http/Client/Adapter/Exception.php';
319
                        throw new Zend_Http_Client_Adapter_Exception("Cannot set a file-handle for cURL option CURLOPT_INFILE without also setting its size in CURLOPT_INFILESIZE.");
320
                    }
321
322
                    if(is_resource($body)) {
0 ignored issues
show
introduced by
The condition is_resource($body) is always false.
Loading history...
323
                        $body = '';
324
                    }
325
326
                    $curlMethod = CURLOPT_PUT;
327
                } else {
328
                    $curlMethod = CURLOPT_CUSTOMREQUEST;
329
                    $curlValue = "PUT";
330
                }
331
                break;
332
333
            case Zend_Http_Client::PATCH:
334
                $curlMethod = CURLOPT_CUSTOMREQUEST;
335
                $curlValue = "PATCH";
336
                break;
337
338
            case Zend_Http_Client::DELETE:
339
                $curlMethod = CURLOPT_CUSTOMREQUEST;
340
                $curlValue = "DELETE";
341
                break;
342
343
            case Zend_Http_Client::OPTIONS:
344
                $curlMethod = CURLOPT_CUSTOMREQUEST;
345
                $curlValue = "OPTIONS";
346
                break;
347
348
            case Zend_Http_Client::TRACE:
349
                $curlMethod = CURLOPT_CUSTOMREQUEST;
350
                $curlValue = "TRACE";
351
                break;
352
353
            case Zend_Http_Client::HEAD:
354
                $curlMethod = CURLOPT_CUSTOMREQUEST;
355
                $curlValue = "HEAD";
356
                break;
357
358
            default:
359
                // For now, through an exception for unsupported request methods
360
                require_once 'Zend/Http/Client/Adapter/Exception.php';
361
                throw new Zend_Http_Client_Adapter_Exception("Method currently not supported");
362
        }
363
364
        if(is_resource($body) && $curlMethod != CURLOPT_PUT) {
0 ignored issues
show
introduced by
The condition is_resource($body) is always false.
Loading history...
365
            require_once 'Zend/Http/Client/Adapter/Exception.php';
366
            throw new Zend_Http_Client_Adapter_Exception("Streaming requests are allowed only with PUT");
367
        }
368
369
        // get http version to use
370
        $curlHttp = ($httpVersion == 1.1) ? CURL_HTTP_VERSION_1_1 : CURL_HTTP_VERSION_1_0;
371
372
        // mark as HTTP request and set HTTP method
373
        curl_setopt($this->_curl, CURLOPT_HTTP_VERSION, $curlHttp);
374
        curl_setopt($this->_curl, $curlMethod, $curlValue);
375
376
        if($this->out_stream) {
377
            // headers will be read into the response
378
            curl_setopt($this->_curl, CURLOPT_HEADER, false);
379
            curl_setopt($this->_curl, CURLOPT_HEADERFUNCTION, array($this, "readHeader"));
380
            // and data will be written into the file
381
            curl_setopt($this->_curl, CURLOPT_FILE, $this->out_stream);
382
        } else {
383
            // ensure headers are also returned
384
            curl_setopt($this->_curl, CURLOPT_HEADER, true);
385
            curl_setopt($this->_curl, CURLINFO_HEADER_OUT, true);
386
387
            // ensure actual response is returned
388
            curl_setopt($this->_curl, CURLOPT_RETURNTRANSFER, true);
389
        }
390
391
        // set additional headers
392
        $headers['Accept'] = '';
393
        curl_setopt($this->_curl, CURLOPT_HTTPHEADER, $headers);
394
395
        /**
396
         * Make sure POSTFIELDS is set after $curlMethod is set:
397
         * @link http://de2.php.net/manual/en/function.curl-setopt.php#81161
398
         */
399
        if ($method == Zend_Http_Client::POST) {
400
            curl_setopt($this->_curl, CURLOPT_POSTFIELDS, $body);
401
        } elseif ($curlMethod == CURLOPT_PUT) {
402
            // this covers a PUT by file-handle:
403
            // Make the setting of this options explicit (rather than setting it through the loop following a bit lower)
404
            // to group common functionality together.
405
            curl_setopt($this->_curl, CURLOPT_INFILE, $this->_config['curloptions'][CURLOPT_INFILE]);
406
            curl_setopt($this->_curl, CURLOPT_INFILESIZE, $this->_config['curloptions'][CURLOPT_INFILESIZE]);
407
            unset($this->_config['curloptions'][CURLOPT_INFILE]);
408
            unset($this->_config['curloptions'][CURLOPT_INFILESIZE]);
409
        } elseif ($method == Zend_Http_Client::PUT) {
410
            // This is a PUT by a setRawData string, not by file-handle
411
            curl_setopt($this->_curl, CURLOPT_POSTFIELDS, $body);
412
        } elseif ($method == Zend_Http_Client::PATCH) {
413
            // This is a PATCH by a setRawData string
414
            curl_setopt($this->_curl, CURLOPT_POSTFIELDS, $body);
415
        } elseif ($method == Zend_Http_Client::DELETE) {
416
            // This is a DELETE by a setRawData string
417
            curl_setopt($this->_curl, CURLOPT_POSTFIELDS, $body);
418
        } elseif ($method == Zend_Http_Client::OPTIONS) {
419
            // This is an OPTIONS by a setRawData string
420
            curl_setopt($this->_curl, CURLOPT_POSTFIELDS, $body);
421
        }
422
423
        // set additional curl options
424
        if (isset($this->_config['curloptions'])) {
425
            foreach ((array)$this->_config['curloptions'] as $k => $v) {
426
                if (!in_array($k, $this->_invalidOverwritableCurlOptions)) {
427
                    if (curl_setopt($this->_curl, $k, $v) == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
428
                        require_once 'Zend/Http/Client/Exception.php';
429
                        throw new Zend_Http_Client_Exception(sprintf("Unknown or erroreous cURL option '%s' set", $k));
430
                    }
431
                }
432
            }
433
        }
434
435
        // send the request
436
        $response = curl_exec($this->_curl);
437
438
        // if we used streaming, headers are already there
439
        if(!is_resource($this->out_stream)) {
440
            $this->_response = $response;
0 ignored issues
show
Documentation Bug introduced by
It seems like $response can also be of type boolean. However, the property $_response is declared as type string. 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 $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

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;
}
Loading history...
441
        }
442
443
        $request  = curl_getinfo($this->_curl, CURLINFO_HEADER_OUT);
444
        $request .= $body;
445
446
        if (empty($this->_response)) {
447
            require_once 'Zend/Http/Client/Exception.php';
448
            throw new Zend_Http_Client_Exception("Error in cURL request: " . curl_error($this->_curl));
449
        }
450
451
        // cURL automatically decodes chunked-messages, this means we have to disallow the Zend_Http_Response to do it again
452
        if (stripos($this->_response, "Transfer-Encoding: chunked\r\n")) {
453
            $this->_response = str_ireplace("Transfer-Encoding: chunked\r\n", '', $this->_response);
454
        }
455
456
        // Eliminate multiple HTTP responses.
457
        do {
458
            $parts  = preg_split('|(?:\r?\n){2}|m', $this->_response, 2);
459
            $again  = false;
460
461
            if (isset($parts[1]) && preg_match("|^HTTP/1\.[01](.*?)\r\n|mi", $parts[1])) {
462
                $this->_response    = $parts[1];
463
                $again              = true;
464
            }
465
        } while ($again);
466
467
        // cURL automatically handles Proxy rewrites, remove the "HTTP/1.0 200 Connection established" string:
468
        if (stripos($this->_response, "HTTP/1.0 200 Connection established\r\n\r\n") !== false) {
469
            $this->_response = str_ireplace("HTTP/1.0 200 Connection established\r\n\r\n", '', $this->_response);
470
        }
471
472
        return $request;
473
    }
474
475
    /**
476
     * Return read response from server
477
     *
478
     * @return string
479
     */
480
    public function read()
481
    {
482
        return $this->_response;
483
    }
484
485
    /**
486
     * Close the connection to the server
487
     *
488
     */
489
    public function close()
490
    {
491
        if(is_resource($this->_curl)) {
492
            curl_close($this->_curl);
493
        }
494
        $this->_curl         = null;
495
        $this->_connected_to = array(null, null);
496
    }
497
498
    /**
499
     * Get cUrl Handle
500
     *
501
     * @return resource
502
     */
503
    public function getHandle()
504
    {
505
        return $this->_curl;
506
    }
507
508
    /**
509
     * Set output stream for the response
510
     *
511
     * @param resource $stream
512
     * @return Zend_Http_Client_Adapter_Socket
513
     */
514
    public function setOutputStream($stream)
515
    {
516
        $this->out_stream = $stream;
517
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type Zend_Http_Client_Adapter_Curl which is incompatible with the documented return type Zend_Http_Client_Adapter_Socket.
Loading history...
518
    }
519
520
    /**
521
     * Header reader function for CURL
522
     *
523
     * @param resource $curl
524
     * @param string $header
525
     * @return int
526
     */
527
    public function readHeader($curl, $header)
528
    {
529
        $this->_response .= $header;
530
        return strlen($header);
531
    }
532
}
533