Completed
Push — master ( 988ea9...868c80 )
by smiley
07:26
created

HTTPOptionsTrait   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 266
Duplicated Lines 3.76 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
dl 10
loc 266
rs 9.6
c 0
b 0
f 0
wmc 35
lcom 1
cbo 1

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
/**
3
 * Trait HTTPOptionsTrait
4
 *
5
 * @filesource   HTTPOptionsTrait.php
6
 * @created      28.08.2018
7
 * @package      chillerlan\HTTP
8
 * @author       Smiley <[email protected]>
9
 * @copyright    2018 Smiley
10
 * @license      MIT
11
 */
12
13
namespace chillerlan\HTTP;
14
15
use chillerlan\HTTP\CurlUtils\CurlHandle;
16
use chillerlan\HTTP\Psr18\ClientException;
17
18
use function file_exists, ini_get, is_array, is_dir, is_file, is_link, is_string, readlink, trim;
19
20
use const CURLOPT_CAINFO, CURLOPT_CAPATH, CURLOPT_SSL_VERIFYHOST, CURLOPT_SSL_VERIFYPEER;
21
22
trait HTTPOptionsTrait{
23
24
	/**
25
	 * @var string
26
	 */
27
	protected string $user_agent = 'chillerlanHttpInterface/2.0 +https://github.com/chillerlan/php-httpinterface';
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected T_STRING, expecting T_FUNCTION or T_CONST
Loading history...
28
29
	/**
30
	 * options for each curl instance
31
	 *
32
	 * this array is being merged into the default options as the last thing before curl_exec().
33
	 * none of the values (except existence of the CA file) will be checked - that's up to the implementation.
34
	 */
35
	protected array $curl_options = [];
36
37
	/**
38
	 * CA Root Certificates for use with CURL/SSL (if not configured in php.ini or available in a default path)
39
	 *
40
	 * @link https://curl.haxx.se/docs/caextract.html
41
	 * @link https://curl.haxx.se/ca/cacert.pem
42
	 * @link https://raw.githubusercontent.com/bagder/ca-bundle/master/ca-bundle.crt
43
	 */
44
	protected ?string $ca_info = null;
45
46
	/**
47
	 * see CURLOPT_SSL_VERIFYPEER
48
	 * requires either HTTPOptions::$ca_info or a properly working system CA file
49
	 *
50
	 * @link https://php.net/manual/function.curl-setopt.php
51
	 */
52
	protected bool $ssl_verifypeer = true;
53
54
	/**
55
	 * The CurlHandleInterface to use in CurlClient::sendRequest()
56
	 */
57
	protected string $curlHandle = CurlHandle::class;
58
59
	/**
60
	 * options for the curl multi instance
61
	 *
62
	 * @link https://www.php.net/manual/function.curl-multi-setopt.php
63
	 */
64
	protected array $curl_multi_options = [];
65
66
	/**
67
	 * maximum of concurrent requests for curl_multi
68
	 */
69
	protected int $windowSize = 5;
70
71
	/**
72
	 * sleep timer (milliseconds) between each fired multi request on startup
73
	 */
74
	protected ?int $sleep = null;
75
76
	/**
77
	 *
78
	 */
79
	protected int $timeout = 10;
80
81
	/**
82
	 *
83
	 */
84
	protected int $retries = 3;
85
86
	/**
87
	 * HTTPOptionsTrait constructor
88
	 *
89
	 * @throws \Psr\Http\Client\ClientExceptionInterface
90
	 */
91
	protected function HTTPOptionsTrait():void{
92
93
		if(!is_array($this->curl_options)){
94
			$this->curl_options = [];
95
		}
96
97
		if(!is_string($this->user_agent) || empty(trim($this->user_agent))){
98
			throw new ClientException('invalid user agent');
99
		}
100
101
		$this->setCA();
102
	}
103
104
	/**
105
	 * @return void
106
	 * @throws \Psr\Http\Client\ClientExceptionInterface
107
	 */
108
	protected function setCA():void{
109
110
		if(!$this->checkVerifyEnabled()){
111
			return;
112
		}
113
114
		// a path/dir/link to a CA bundle is given, let's check that
115
		if(is_string($this->ca_info)){
116
117
			if($this->setCaBundle()){
118
				return;
119
			}
120
121
			throw new ClientException('invalid path to SSL CA bundle (HTTPOptions::$ca_info): '.$this->ca_info);
122
		}
123
124
		// we somehow landed here, so let's check if there's a CA bundle given via the cURL options
125
		$ca = $this->curl_options[CURLOPT_CAPATH] ?? $this->curl_options[CURLOPT_CAINFO] ?? false;
126
127
		if($ca){
128
129
			if($this->setCaBundleCurl($ca)){
130
				return;
131
			}
132
133
			throw new ClientException('invalid path to SSL CA bundle (CURLOPT_CAPATH/CURLOPT_CAINFO): '.$ca);
134
		}
135
136
		// check php.ini options - PHP should find the file by itself
137
		if(file_exists(ini_get('curl.cainfo'))){
138
			return; // @codeCoverageIgnore
139
		}
140
141
		// this is getting weird. as a last resort, we're going to check some default paths for a CA bundle file
142
		if($this->checkCaDefaultLocations()){
143
			return;
144
		}
145
146
		// @codeCoverageIgnoreStart
147
		$msg = 'No system CA bundle could be found in any of the the common system locations. '
148
		       .'In order to verify peer certificates, you will need to supply the path on disk to a certificate bundle via  '
149
		       .'HTTPOptions::$ca_info or HTTPOptions::$curl_options. If you do not need a specific certificate bundle, '
150
		       .'then you can download a CA bundle over here: https://curl.haxx.se/docs/caextract.html. '
151
		       .'Once you have a CA bundle available on disk, you can set the "curl.cainfo" php.ini setting to point '
152
		       .'to the path of the file, allowing you to omit the $ca_info or $curl_options setting. '
153
		       .'See http://curl.haxx.se/docs/sslcerts.html for more information.';
154
155
		throw new ClientException($msg);
156
		// @codeCoverageIgnoreEnd
157
	}
158
159
	/**
160
	 * @return bool
161
	 */
162
	protected function checkCaDefaultLocations():bool{
163
164
		$cafiles = [
165
			// check other php.ini settings
166
			ini_get('openssl.cafile'),
167
			// Red Hat, CentOS, Fedora (provided by the ca-certificates package)
168
			'/etc/pki/tls/certs/ca-bundle.crt',
169
			// Ubuntu, Debian (provided by the ca-certificates package)
170
			'/etc/ssl/certs/ca-certificates.crt',
171
			// FreeBSD (provided by the ca_root_nss package)
172
			'/usr/local/share/certs/ca-root-nss.crt',
173
			// SLES 12 (provided by the ca-certificates package)
174
			'/var/lib/ca-certificates/ca-bundle.pem',
175
			// OS X provided by homebrew (using the default path)
176
			'/usr/local/etc/openssl/cert.pem',
177
			// Google app engine
178
			'/etc/ca-certificates.crt',
179
			// Windows?
180
			// http://php.net/manual/en/function.curl-setopt.php#110457
181
			'C:\\Windows\\system32\\curl-ca-bundle.crt',
182
			'C:\\Windows\\curl-ca-bundle.crt',
183
			'C:\\Windows\\system32\\cacert.pem',
184
			'C:\\Windows\\cacert.pem',
185
			// working path
186
			__DIR__.'/cacert.pem',
187
		];
188
189
		foreach($cafiles as $file){
190
191
			if(is_file($file) || (is_link($file) && is_file(readlink($file)))){
192
				$this->curl_options[CURLOPT_CAINFO] = $file;
193
				$this->ca_info                      = $file;
194
195
				return true;
196
			}
197
198
		}
199
200
		return false; // @codeCoverageIgnore
201
	}
202
203
	/**
204
	 * @return bool
205
	 */
206
	protected function checkVerifyEnabled():bool{
207
208
		// disable verification if wanted so
209
		if($this->ssl_verifypeer !== true || (isset($this->curl_options[CURLOPT_SSL_VERIFYPEER]) && !$this->curl_options[CURLOPT_SSL_VERIFYPEER])){
210
			unset($this->curl_options[CURLOPT_CAINFO], $this->curl_options[CURLOPT_CAPATH]);
211
212
			$this->curl_options[CURLOPT_SSL_VERIFYHOST] = 0;
213
			$this->curl_options[CURLOPT_SSL_VERIFYPEER] = false;
214
215
			return false;
216
		}
217
218
		$this->curl_options[CURLOPT_SSL_VERIFYHOST] = 2;
219
		$this->curl_options[CURLOPT_SSL_VERIFYPEER] = true;
220
221
		return true;
222
	}
223
224
	/**
225
	 * @return bool
226
	 */
227
	protected function setCaBundle():bool{
228
229
		// if you - for whatever obscure reason - need to check Windows .lnk links,
230
		// see http://php.net/manual/en/function.is-link.php#91249
231
		switch(true){
232
			case is_dir($this->ca_info):
233
			case is_link($this->ca_info) && is_dir(readlink($this->ca_info)): // @codeCoverageIgnore
234
				$this->curl_options[CURLOPT_CAPATH] = $this->ca_info;
235
				unset($this->curl_options[CURLOPT_CAINFO]);
236
237
				return true;
238
239
			case is_file($this->ca_info):
240
			case is_link($this->ca_info) && is_file(readlink($this->ca_info)): // @codeCoverageIgnore
241
				$this->curl_options[CURLOPT_CAINFO] = $this->ca_info;
242
				unset($this->curl_options[CURLOPT_CAPATH]);
243
244
				return true;
245
		}
246
247
		return false;
248
	}
249
250
	/**
251
	 * @param string $ca
252
	 *
253
	 * @return bool
254
	 */
255
	protected function setCaBundleCurl(string $ca):bool{
256
257
		// just check if the file/path exists
258
		switch(true){
259
			case is_dir($ca):
260
			case is_link($ca) && is_dir(readlink($ca)): // @codeCoverageIgnore
261
				unset($this->curl_options[CURLOPT_CAINFO]);
262
263
				return true;
264
265
			case is_file($ca):
266
			case is_link($ca) && is_file(readlink($ca)): // @codeCoverageIgnore
267
268
				return true;
269
		}
270
271
		return false;
272
	}
273
274
}
275