1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org) |
4
|
|
|
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
5
|
|
|
* |
6
|
|
|
* Licensed under The MIT License |
7
|
|
|
* Redistributions of files must retain the above copyright notice. |
8
|
|
|
* |
9
|
|
|
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
10
|
|
|
* @link https://cakephp.org CakePHP(tm) Project |
11
|
|
|
* @since 3.7.0 |
12
|
|
|
* @license https://opensource.org/licenses/mit-license.php MIT License |
13
|
|
|
*/ |
14
|
|
|
namespace Cake\Http\Client\Adapter; |
15
|
|
|
|
16
|
|
|
use Cake\Http\Client\AdapterInterface; |
17
|
|
|
use Cake\Http\Client\Request; |
18
|
|
|
use Cake\Http\Client\Response; |
19
|
|
|
use Cake\Http\Exception\HttpException; |
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* Implements sending Cake\Http\Client\Request via ext/curl. |
23
|
|
|
* |
24
|
|
|
* In addition to the standard options documented in Cake\Http\Client, |
25
|
|
|
* this adapter supports all available curl options. Additional curl options |
26
|
|
|
* can be set via the `curl` option key when making requests or configuring |
27
|
|
|
* a client. |
28
|
|
|
*/ |
29
|
|
|
class Curl implements AdapterInterface |
30
|
|
|
{ |
31
|
|
|
/** |
32
|
|
|
* {@inheritDoc} |
33
|
|
|
*/ |
34
|
|
|
public function send(Request $request, array $options) |
35
|
|
|
{ |
36
|
|
|
$ch = curl_init(); |
37
|
|
|
$options = $this->buildOptions($request, $options); |
38
|
|
|
curl_setopt_array($ch, $options); |
39
|
|
|
|
40
|
|
|
$body = $this->exec($ch); |
41
|
|
|
if ($body === false) { |
42
|
|
|
$errorCode = curl_errno($ch); |
43
|
|
|
$error = curl_error($ch); |
44
|
|
|
curl_close($ch); |
45
|
|
|
|
46
|
|
|
$status = 500; |
47
|
|
|
if ($errorCode === CURLE_OPERATION_TIMEOUTED) { |
48
|
|
|
$status = 504; |
49
|
|
|
} |
50
|
|
|
throw new HttpException("cURL Error ({$errorCode}) {$error}", $status); |
51
|
|
|
} |
52
|
|
|
|
53
|
|
|
$responses = $this->createResponse($ch, $body); |
54
|
|
|
curl_close($ch); |
55
|
|
|
|
56
|
|
|
return $responses; |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* Convert client options into curl options. |
61
|
|
|
* |
62
|
|
|
* @param \Cake\Http\Client\Request $request The request. |
63
|
|
|
* @param array $options The client options |
64
|
|
|
* @return array |
65
|
|
|
*/ |
66
|
|
|
public function buildOptions(Request $request, array $options) |
67
|
|
|
{ |
68
|
|
|
$headers = []; |
69
|
|
View Code Duplication |
foreach ($request->getHeaders() as $key => $values) { |
70
|
|
|
$headers[] = $key . ': ' . implode(', ', $values); |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
$out = [ |
74
|
|
|
CURLOPT_URL => (string)$request->getUri(), |
75
|
|
|
CURLOPT_HTTP_VERSION => $this->getProtocolVersion($request), |
76
|
|
|
CURLOPT_RETURNTRANSFER => true, |
77
|
|
|
CURLOPT_HEADER => true, |
78
|
|
|
CURLOPT_HTTPHEADER => $headers, |
79
|
|
|
]; |
80
|
|
|
switch ($request->getMethod()) { |
81
|
|
|
case Request::METHOD_GET: |
82
|
|
|
$out[CURLOPT_HTTPGET] = true; |
83
|
|
|
break; |
84
|
|
|
|
85
|
|
|
case Request::METHOD_POST: |
86
|
|
|
$out[CURLOPT_POST] = true; |
87
|
|
|
break; |
88
|
|
|
|
89
|
|
|
default: |
90
|
|
|
$out[CURLOPT_POST] = true; |
91
|
|
|
$out[CURLOPT_CUSTOMREQUEST] = $request->getMethod(); |
92
|
|
|
break; |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
$body = $request->getBody(); |
96
|
|
|
if ($body) { |
97
|
|
|
$body->rewind(); |
98
|
|
|
$out[CURLOPT_POSTFIELDS] = $body->getContents(); |
99
|
|
|
} |
100
|
|
|
|
101
|
|
View Code Duplication |
if (empty($options['ssl_cafile'])) { |
102
|
|
|
$options['ssl_cafile'] = CORE_PATH . 'config' . DIRECTORY_SEPARATOR . 'cacert.pem'; |
103
|
|
|
} |
104
|
|
|
if (!empty($options['ssl_verify_host'])) { |
105
|
|
|
// Value of 1 or true is deprecated. Only 2 or 0 should be used now. |
106
|
|
|
$options['ssl_verify_host'] = 2; |
107
|
|
|
} |
108
|
|
|
$optionMap = [ |
109
|
|
|
'timeout' => CURLOPT_TIMEOUT, |
110
|
|
|
'ssl_verify_peer' => CURLOPT_SSL_VERIFYPEER, |
111
|
|
|
'ssl_verify_host' => CURLOPT_SSL_VERIFYHOST, |
112
|
|
|
'ssl_cafile' => CURLOPT_CAINFO, |
113
|
|
|
'ssl_local_cert' => CURLOPT_SSLCERT, |
114
|
|
|
'ssl_passphrase' => CURLOPT_SSLCERTPASSWD, |
115
|
|
|
]; |
116
|
|
|
foreach ($optionMap as $option => $curlOpt) { |
117
|
|
|
if (isset($options[$option])) { |
118
|
|
|
$out[$curlOpt] = $options[$option]; |
119
|
|
|
} |
120
|
|
|
} |
121
|
|
|
if (isset($options['proxy']['proxy'])) { |
122
|
|
|
$out[CURLOPT_PROXY] = $options['proxy']['proxy']; |
123
|
|
|
} |
124
|
|
|
if (isset($options['proxy']['username'])) { |
125
|
|
|
$password = !empty($options['proxy']['password']) ? $options['proxy']['password'] : ''; |
126
|
|
|
$out[CURLOPT_PROXYUSERPWD] = $options['proxy']['username'] . ':' . $password; |
127
|
|
|
} |
128
|
|
|
if (isset($options['curl']) && is_array($options['curl'])) { |
129
|
|
|
// Can't use array_merge() because keys will be re-ordered. |
130
|
|
|
foreach ($options['curl'] as $key => $value) { |
131
|
|
|
$out[$key] = $value; |
132
|
|
|
} |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
return $out; |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
/** |
139
|
|
|
* Convert HTTP version number into curl value. |
140
|
|
|
* |
141
|
|
|
* @param \Cake\Http\Client\Request $request The request to get a protocol version for. |
142
|
|
|
* @return int |
143
|
|
|
*/ |
144
|
|
|
protected function getProtocolVersion(Request $request) |
145
|
|
|
{ |
146
|
|
|
switch ($request->getProtocolVersion()) { |
147
|
|
|
case '1.0': |
148
|
|
|
return CURL_HTTP_VERSION_1_0; |
149
|
|
|
case '1.1': |
150
|
|
|
return CURL_HTTP_VERSION_1_1; |
151
|
|
|
case '2': |
152
|
|
|
case '2.0': |
153
|
|
|
if (defined('CURL_HTTP_VERSION_2TLS')) { |
154
|
|
|
return CURL_HTTP_VERSION_2TLS; |
155
|
|
|
} |
156
|
|
|
if (defined('CURL_HTTP_VERSION_2_0')) { |
157
|
|
|
return CURL_HTTP_VERSION_2_0; |
158
|
|
|
} |
159
|
|
|
throw new HttpException('libcurl 7.33 or greater required for HTTP/2 support'); |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
return CURL_HTTP_VERSION_NONE; |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
/** |
166
|
|
|
* Convert the raw curl response into an Http\Client\Response |
167
|
|
|
* |
168
|
|
|
* @param resource $handle Curl handle |
169
|
|
|
* @param string $responseData string The response data from curl_exec |
170
|
|
|
* @return \Cake\Http\Client\Response |
171
|
|
|
*/ |
172
|
|
|
protected function createResponse($handle, $responseData) |
173
|
|
|
{ |
174
|
|
|
$headerSize = curl_getinfo($handle, CURLINFO_HEADER_SIZE); |
175
|
|
|
$headers = trim(substr($responseData, 0, $headerSize)); |
176
|
|
|
$body = substr($responseData, $headerSize); |
177
|
|
|
$response = new Response(explode("\r\n", $headers), $body); |
178
|
|
|
|
179
|
|
|
return [$response]; |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
/** |
183
|
|
|
* Execute the curl handle. |
184
|
|
|
* |
185
|
|
|
* @param resource $ch Curl Resource handle |
186
|
|
|
* @return string |
187
|
|
|
*/ |
188
|
|
|
protected function exec($ch) |
189
|
|
|
{ |
190
|
|
|
return curl_exec($ch); |
191
|
|
|
} |
192
|
|
|
} |
193
|
|
|
|