Client::preparePharCacert()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
3
namespace Guzzle\Http;
4
5
use Guzzle\Common\Collection;
6
use Guzzle\Common\AbstractHasDispatcher;
7
use Guzzle\Common\Exception\ExceptionCollection;
8
use Guzzle\Common\Exception\InvalidArgumentException;
9
use Guzzle\Common\Exception\RuntimeException;
10
use Guzzle\Common\Version;
11
use Guzzle\Parser\ParserRegistry;
12
use Guzzle\Parser\UriTemplate\UriTemplateInterface;
13
use Guzzle\Http\Message\RequestInterface;
14
use Guzzle\Http\Message\RequestFactory;
15
use Guzzle\Http\Message\RequestFactoryInterface;
16
use Guzzle\Http\Curl\CurlMultiInterface;
17
use Guzzle\Http\Curl\CurlMultiProxy;
18
use Guzzle\Http\Curl\CurlHandle;
19
use Guzzle\Http\Curl\CurlVersion;
20
21
/**
22
 * HTTP client
23
 */
24
class Client extends AbstractHasDispatcher implements ClientInterface
25
{
26
    /** @deprecated Use [request.options][params] */
27
    const REQUEST_PARAMS = 'request.params';
28
29
    const REQUEST_OPTIONS = 'request.options';
30
    const CURL_OPTIONS = 'curl.options';
31
    const SSL_CERT_AUTHORITY = 'ssl.certificate_authority';
32
    const DISABLE_REDIRECTS = RedirectPlugin::DISABLE;
33
    const DEFAULT_SELECT_TIMEOUT = 1.0;
34
    const MAX_HANDLES = 3;
35
36
    /** @var Collection Default HTTP headers to set on each request */
37
    protected $defaultHeaders;
38
39
    /** @var string The user agent string to set on each request */
40
    protected $userAgent;
41
42
    /** @var Collection Parameter object holding configuration data */
43
    private $config;
44
45
    /** @var Url Base URL of the client */
46
    private $baseUrl;
47
48
    /** @var CurlMultiInterface CurlMulti object used internally */
49
    private $curlMulti;
50
51
    /** @var UriTemplateInterface URI template owned by the client */
52
    private $uriTemplate;
53
54
    /** @var RequestFactoryInterface Request factory used by the client */
55
    protected $requestFactory;
56
57
    public static function getAllEvents()
58
    {
59
        return array(self::CREATE_REQUEST);
60
    }
61
62
    /**
63
     * @param string           $baseUrl Base URL of the web service
64
     * @param array|Collection $config  Configuration settings
65
     *
66
     * @throws RuntimeException if cURL is not installed
67
     */
68
    public function __construct($baseUrl = '', $config = null)
69
    {
70
        if (!extension_loaded('curl')) {
71
            // @codeCoverageIgnoreStart
72
            throw new RuntimeException('The PHP cURL extension must be installed to use Guzzle.');
73
            // @codeCoverageIgnoreEnd
74
        }
75
        $this->setConfig($config ?: new Collection());
76
        $this->initSsl();
77
        $this->setBaseUrl($baseUrl);
78
        $this->defaultHeaders = new Collection();
79
        $this->setRequestFactory(RequestFactory::getInstance());
80
        $this->userAgent = $this->getDefaultUserAgent();
81
        if (!$this->config[self::DISABLE_REDIRECTS]) {
82
            $this->addSubscriber(new RedirectPlugin());
83
        }
84
    }
85
86 View Code Duplication
    final public function setConfig($config)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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.

Loading history...
87
    {
88
        if ($config instanceof Collection) {
89
            $this->config = $config;
90
        } elseif (is_array($config)) {
91
            $this->config = new Collection($config);
92
        } else {
93
            throw new InvalidArgumentException('Config must be an array or Collection');
94
        }
95
96
        return $this;
97
    }
98
99
    final public function getConfig($key = false)
100
    {
101
        return $key ? $this->config[$key] : $this->config;
102
    }
103
104
    /**
105
     * Set a default request option on the client that will be used as a default for each request
106
     *
107
     * @param string $keyOrPath request.options key (e.g. allow_redirects) or path to a nested key (e.g. headers/foo)
108
     * @param mixed  $value     Value to set
109
     *
110
     * @return $this
111
     */
112
    public function setDefaultOption($keyOrPath, $value)
113
    {
114
        $keyOrPath = self::REQUEST_OPTIONS . '/' . $keyOrPath;
115
        $this->config->setPath($keyOrPath, $value);
116
117
        return $this;
118
    }
119
120
    /**
121
     * Retrieve a default request option from the client
122
     *
123
     * @param string $keyOrPath request.options key (e.g. allow_redirects) or path to a nested key (e.g. headers/foo)
124
     *
125
     * @return mixed|null
126
     */
127
    public function getDefaultOption($keyOrPath)
128
    {
129
        $keyOrPath = self::REQUEST_OPTIONS . '/' . $keyOrPath;
130
131
        return $this->config->getPath($keyOrPath);
132
    }
133
134
    final public function setSslVerification($certificateAuthority = true, $verifyPeer = true, $verifyHost = 2)
135
    {
136
        $opts = $this->config[self::CURL_OPTIONS] ?: array();
137
138
        if ($certificateAuthority === true) {
139
            // use bundled CA bundle, set secure defaults
140
            $opts[CURLOPT_CAINFO] = __DIR__ . '/Resources/cacert.pem';
141
            $opts[CURLOPT_SSL_VERIFYPEER] = true;
142
            $opts[CURLOPT_SSL_VERIFYHOST] = 2;
143 View Code Duplication
        } elseif ($certificateAuthority === false) {
0 ignored issues
show
Duplication introduced by
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.

Loading history...
144
            unset($opts[CURLOPT_CAINFO]);
145
            $opts[CURLOPT_SSL_VERIFYPEER] = false;
146
            $opts[CURLOPT_SSL_VERIFYHOST] = 0;
147
        } elseif ($verifyPeer !== true && $verifyPeer !== false && $verifyPeer !== 1 && $verifyPeer !== 0) {
148
            throw new InvalidArgumentException('verifyPeer must be 1, 0 or boolean');
149
        } elseif ($verifyHost !== 0 && $verifyHost !== 1 && $verifyHost !== 2) {
150
            throw new InvalidArgumentException('verifyHost must be 0, 1 or 2');
151
        } else {
152
            $opts[CURLOPT_SSL_VERIFYPEER] = $verifyPeer;
153
            $opts[CURLOPT_SSL_VERIFYHOST] = $verifyHost;
154
            if (is_file($certificateAuthority)) {
155
                unset($opts[CURLOPT_CAPATH]);
156
                $opts[CURLOPT_CAINFO] = $certificateAuthority;
157
            } elseif (is_dir($certificateAuthority)) {
158
                unset($opts[CURLOPT_CAINFO]);
159
                $opts[CURLOPT_CAPATH] = $certificateAuthority;
160
            } else {
161
                throw new RuntimeException(
162
                    'Invalid option passed to ' . self::SSL_CERT_AUTHORITY . ': ' . $certificateAuthority
163
                );
164
            }
165
        }
166
167
        $this->config->set(self::CURL_OPTIONS, $opts);
168
169
        return $this;
170
    }
171
172
    public function createRequest($method = 'GET', $uri = null, $headers = null, $body = null, array $options = array())
173
    {
174
        if (!$uri) {
175
            $url = $this->getBaseUrl();
176
        } else {
177
            if (!is_array($uri)) {
178
                $templateVars = null;
179
            } else {
180
                list($uri, $templateVars) = $uri;
181
            }
182
            if (strpos($uri, '://')) {
183
                // Use absolute URLs as-is
184
                $url = $this->expandTemplate($uri, $templateVars);
185
            } else {
186
                $url = Url::factory($this->getBaseUrl())->combine($this->expandTemplate($uri, $templateVars));
0 ignored issues
show
Bug introduced by
It seems like $this->getBaseUrl() targeting Guzzle\Http\Client::getBaseUrl() can also be of type object<Guzzle\Http\Url>; however, Guzzle\Http\Url::factory() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
187
            }
188
        }
189
190
        // If default headers are provided, then merge them under any explicitly provided headers for the request
191
        if (count($this->defaultHeaders)) {
192
            if (!$headers) {
193
                $headers = $this->defaultHeaders->toArray();
194
            } elseif (is_array($headers)) {
195
                $headers += $this->defaultHeaders->toArray();
196
            } elseif ($headers instanceof Collection) {
197
                $headers = $headers->toArray() + $this->defaultHeaders->toArray();
198
            }
199
        }
200
201
        return $this->prepareRequest($this->requestFactory->create($method, (string) $url, $headers, $body), $options);
202
    }
203
204
    public function getBaseUrl($expand = true)
205
    {
206
        return $expand ? $this->expandTemplate($this->baseUrl) : $this->baseUrl;
0 ignored issues
show
Bug Compatibility introduced by
The expression $expand ? $this->expandT...eUrl) : $this->baseUrl; of type string|Guzzle\Http\Url adds the type Guzzle\Http\Url to the return on line 206 which is incompatible with the return type declared by the interface Guzzle\Http\ClientInterface::getBaseUrl of type string|null.
Loading history...
207
    }
208
209
    public function setBaseUrl($url)
210
    {
211
        $this->baseUrl = $url;
0 ignored issues
show
Documentation Bug introduced by
It seems like $url of type string is incompatible with the declared type object<Guzzle\Http\Url> of property $baseUrl.

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..

Loading history...
212
213
        return $this;
214
    }
215
216
    public function setUserAgent($userAgent, $includeDefault = false)
217
    {
218
        if ($includeDefault) {
219
            $userAgent .= ' ' . $this->getDefaultUserAgent();
220
        }
221
        $this->userAgent = $userAgent;
222
223
        return $this;
224
    }
225
226
    /**
227
     * Get the default User-Agent string to use with Guzzle
228
     *
229
     * @return string
230
     */
231
    public function getDefaultUserAgent()
232
    {
233
        return 'Guzzle/' . Version::VERSION
234
            . ' curl/' . CurlVersion::getInstance()->get('version')
235
            . ' PHP/' . PHP_VERSION;
236
    }
237
238
    public function get($uri = null, $headers = null, $options = array())
239
    {
240
        // BC compat: $options can be a string, resource, etc to specify where the response body is downloaded
241
        return is_array($options)
242
            ? $this->createRequest('GET', $uri, $headers, null, $options)
243
            : $this->createRequest('GET', $uri, $headers, $options);
244
    }
245
246
    public function head($uri = null, $headers = null, array $options = array())
247
    {
248
        return $this->createRequest('HEAD', $uri, $headers, null, $options);
249
    }
250
251
    public function delete($uri = null, $headers = null, $body = null, array $options = array())
252
    {
253
        return $this->createRequest('DELETE', $uri, $headers, $body, $options);
254
    }
255
256
    public function put($uri = null, $headers = null, $body = null, array $options = array())
257
    {
258
        return $this->createRequest('PUT', $uri, $headers, $body, $options);
259
    }
260
261
    public function patch($uri = null, $headers = null, $body = null, array $options = array())
262
    {
263
        return $this->createRequest('PATCH', $uri, $headers, $body, $options);
264
    }
265
266
    public function post($uri = null, $headers = null, $postBody = null, array $options = array())
267
    {
268
        return $this->createRequest('POST', $uri, $headers, $postBody, $options);
269
    }
270
271
    public function options($uri = null, array $options = array())
272
    {
273
        return $this->createRequest('OPTIONS', $uri, $options);
274
    }
275
276
    public function send($requests)
277
    {
278
        if (!($requests instanceof RequestInterface)) {
279
            return $this->sendMultiple($requests);
280
        }
281
282
        try {
283
            /** @var $requests RequestInterface  */
284
            $this->getCurlMulti()->add($requests)->send();
285
            return $requests->getResponse();
286
        } catch (ExceptionCollection $e) {
287
            throw $e->getFirst();
288
        }
289
    }
290
291
    /**
292
     * Set a curl multi object to be used internally by the client for transferring requests.
293
     *
294
     * @param CurlMultiInterface $curlMulti Multi object
295
     *
296
     * @return self
297
     */
298
    public function setCurlMulti(CurlMultiInterface $curlMulti)
299
    {
300
        $this->curlMulti = $curlMulti;
301
302
        return $this;
303
    }
304
305
    /**
306
     * @return CurlMultiInterface|CurlMultiProxy
307
     */
308
    public function getCurlMulti()
309
    {
310
        if (!$this->curlMulti) {
311
            $this->curlMulti = new CurlMultiProxy(
312
                self::MAX_HANDLES,
313
                $this->getConfig('select_timeout') ?: self::DEFAULT_SELECT_TIMEOUT
0 ignored issues
show
Documentation introduced by
'select_timeout' is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
314
            );
315
        }
316
317
        return $this->curlMulti;
318
    }
319
320
    public function setRequestFactory(RequestFactoryInterface $factory)
321
    {
322
        $this->requestFactory = $factory;
323
324
        return $this;
325
    }
326
327
    /**
328
     * Set the URI template expander to use with the client
329
     *
330
     * @param UriTemplateInterface $uriTemplate URI template expander
331
     *
332
     * @return self
333
     */
334
    public function setUriTemplate(UriTemplateInterface $uriTemplate)
335
    {
336
        $this->uriTemplate = $uriTemplate;
337
338
        return $this;
339
    }
340
341
    /**
342
     * Expand a URI template while merging client config settings into the template variables
343
     *
344
     * @param string $template  Template to expand
345
     * @param array  $variables Variables to inject
346
     *
347
     * @return string
348
     */
349
    protected function expandTemplate($template, array $variables = null)
350
    {
351
        $expansionVars = $this->getConfig()->toArray();
352
        if ($variables) {
353
            $expansionVars = $variables + $expansionVars;
354
        }
355
356
        return $this->getUriTemplate()->expand($template, $expansionVars);
357
    }
358
359
    /**
360
     * Get the URI template expander used by the client
361
     *
362
     * @return UriTemplateInterface
363
     */
364
    protected function getUriTemplate()
365
    {
366
        if (!$this->uriTemplate) {
367
            $this->uriTemplate = ParserRegistry::getInstance()->getParser('uri_template');
368
        }
369
370
        return $this->uriTemplate;
371
    }
372
373
    /**
374
     * Send multiple requests in parallel
375
     *
376
     * @param array $requests Array of RequestInterface objects
377
     *
378
     * @return array Returns an array of Response objects
379
     */
380
    protected function sendMultiple(array $requests)
381
    {
382
        $curlMulti = $this->getCurlMulti();
383
        foreach ($requests as $request) {
384
            $curlMulti->add($request);
385
        }
386
        $curlMulti->send();
387
388
        /** @var $request RequestInterface */
389
        $result = array();
390
        foreach ($requests as $request) {
391
            $result[] = $request->getResponse();
392
        }
393
394
        return $result;
395
    }
396
397
    /**
398
     * Prepare a request to be sent from the Client by adding client specific behaviors and properties to the request.
399
     *
400
     * @param RequestInterface $request Request to prepare for the client
401
     * @param array            $options Options to apply to the request
402
     *
403
     * @return RequestInterface
404
     */
405
    protected function prepareRequest(RequestInterface $request, array $options = array())
406
    {
407
        $request->setClient($this)->setEventDispatcher(clone $this->getEventDispatcher());
408
409
        if ($curl = $this->config[self::CURL_OPTIONS]) {
410
            $request->getCurlOptions()->overwriteWith(CurlHandle::parseCurlConfig($curl));
411
        }
412
413
        if ($params = $this->config[self::REQUEST_PARAMS]) {
0 ignored issues
show
Deprecated Code introduced by
The constant Guzzle\Http\Client::REQUEST_PARAMS has been deprecated with message: Use [request.options][params]

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
414
            Version::warn('request.params is deprecated. Use request.options to add default request options.');
415
            $request->getParams()->overwriteWith($params);
416
        }
417
418
        if ($this->userAgent && !$request->hasHeader('User-Agent')) {
419
            $request->setHeader('User-Agent', $this->userAgent);
420
        }
421
422
        if ($defaults = $this->config[self::REQUEST_OPTIONS]) {
423
            $this->requestFactory->applyOptions($request, $defaults, RequestFactoryInterface::OPTIONS_AS_DEFAULTS);
424
        }
425
426
        if ($options) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $options 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 empty(..) or ! empty(...) instead.

Loading history...
427
            $this->requestFactory->applyOptions($request, $options);
428
        }
429
430
        $this->dispatch('client.create_request', array('client' => $this, 'request' => $request));
431
432
        return $request;
433
    }
434
435
    /**
436
     * Initializes SSL settings
437
     */
438
    protected function initSsl()
439
    {
440
        $authority = $this->config[self::SSL_CERT_AUTHORITY];
441
442
        if ($authority === 'system') {
443
            return;
444
        }
445
446
        if ($authority === null) {
447
            $authority = true;
448
        }
449
450
        if ($authority === true && substr(__FILE__, 0, 7) == 'phar://') {
451
            $authority = self::extractPharCacert(__DIR__ . '/Resources/cacert.pem');
452
        }
453
454
        $this->setSslVerification($authority);
455
    }
456
457
    /**
458
     * @deprecated
459
     */
460
    public function getDefaultHeaders()
461
    {
462
        Version::warn(__METHOD__ . ' is deprecated. Use the request.options array to retrieve default request options');
463
        return $this->defaultHeaders;
464
    }
465
466
    /**
467
     * @deprecated
468
     */
469 View Code Duplication
    public function setDefaultHeaders($headers)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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.

Loading history...
470
    {
471
        Version::warn(__METHOD__ . ' is deprecated. Use the request.options array to specify default request options');
472
        if ($headers instanceof Collection) {
473
            $this->defaultHeaders = $headers;
474
        } elseif (is_array($headers)) {
475
            $this->defaultHeaders = new Collection($headers);
476
        } else {
477
            throw new InvalidArgumentException('Headers must be an array or Collection');
478
        }
479
480
        return $this;
481
    }
482
483
    /**
484
     * @deprecated
485
     */
486
    public function preparePharCacert($md5Check = true)
0 ignored issues
show
Unused Code introduced by
The parameter $md5Check is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
487
    {
488
        return sys_get_temp_dir() . '/guzzle-cacert.pem';
489
    }
490
491
    /**
492
     * Copies the phar cacert from a phar into the temp directory.
493
     *
494
     * @param string $pharCacertPath Path to the phar cacert. For example:
495
     *                               'phar://aws.phar/Guzzle/Http/Resources/cacert.pem'
496
     *
497
     * @return string Returns the path to the extracted cacert file.
498
     * @throws \RuntimeException Throws if the phar cacert cannot be found or
499
     *                           the file cannot be copied to the temp dir.
500
     */
501
    public static function extractPharCacert($pharCacertPath)
502
    {
503
        // Copy the cacert.pem file from the phar if it is not in the temp
504
        // folder.
505
        $certFile = sys_get_temp_dir() . '/guzzle-cacert.pem';
506
507
        if (!file_exists($pharCacertPath)) {
508
            throw new \RuntimeException("Could not find $pharCacertPath");
509
        }
510
511
        if (!file_exists($certFile) ||
512
            filesize($certFile) != filesize($pharCacertPath)
513
        ) {
514
            if (!copy($pharCacertPath, $certFile)) {
515
                throw new \RuntimeException(
516
                    "Could not copy {$pharCacertPath} to {$certFile}: "
517
                    . var_export(error_get_last(), true)
518
                );
519
            }
520
        }
521
522
        return $certFile;
523
    }
524
}
525