2
namespace GuzzleHttp\Adapter\Curl;
4
use GuzzleHttp\Adapter\TransactionInterface;
5
use GuzzleHttp\Exception\AdapterException;
6
use GuzzleHttp\Message\MessageFactoryInterface;
7
use GuzzleHttp\Message\RequestInterface;
8
use GuzzleHttp\Stream\LazyOpenStream;
9
use GuzzleHttp\Stream\Stream;
12
* Creates curl resources from a request and response object
17
* Creates a cURL handle based on a transaction.
19
* @param TransactionInterface $transaction Holds a request and response
20
* @param MessageFactoryInterface $messageFactory Used to create responses
21
* @param null|resource $handle Optionally provide a curl handle to modify
23
* @return resource Returns a prepared cURL handle
24
* @throws AdapterException when an option cannot be applied
26
public function __invoke(
27
TransactionInterface $transaction,
28
MessageFactoryInterface $messageFactory,
31
$request = $transaction->getRequest();
32
$mediator = new RequestMediator($transaction, $messageFactory);
33
$options = $this->getDefaultOptions($request, $mediator);
34
$this->applyMethod($request, $options);
35
$this->applyTransferOptions($request, $mediator, $options);
36
$this->applyHeaders($request, $options);
37
unset($options['_headers']);
39
// Add adapter options from the request's configuration options
40
if ($config = $request->getConfig()['curl']) {
41
$options = $this->applyCustomCurlOptions($config, $options);
45
$handle = curl_init();
48
curl_setopt_array($handle, $options);
53
protected function getDefaultOptions(
54
RequestInterface $request,
55
RequestMediator $mediator
57
$url = $request->getUrl();
59
// Strip fragment from URL. See:
60
// https://github.com/guzzle/guzzle/issues/453
61
if (($pos = strpos($url, '#')) !== false) {
62
$url = substr($url, 0, $pos);
65
$config = $request->getConfig();
68
CURLOPT_CONNECTTIMEOUT => $config['connect_timeout'] ?: 150,
69
CURLOPT_RETURNTRANSFER => false,
70
CURLOPT_HEADER => false,
71
CURLOPT_WRITEFUNCTION => [$mediator, 'writeResponseBody'],
72
CURLOPT_HEADERFUNCTION => [$mediator, 'receiveResponseHeader'],
73
CURLOPT_READFUNCTION => [$mediator, 'readRequestBody'],
74
CURLOPT_HTTP_VERSION => $request->getProtocolVersion() === '1.0'
75
? CURL_HTTP_VERSION_1_0 : CURL_HTTP_VERSION_1_1,
76
CURLOPT_SSL_VERIFYPEER => 1,
77
CURLOPT_SSL_VERIFYHOST => 2,
78
'_headers' => $request->getHeaders()
81
if (defined('CURLOPT_PROTOCOLS')) {
82
// Allow only HTTP and HTTPS protocols
83
$options[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
86
// cURL sometimes adds a content-type by default. Prevent this.
87
if (!$request->hasHeader('Content-Type')) {
88
$options[CURLOPT_HTTPHEADER][] = 'Content-Type:';
94
private function applyMethod(RequestInterface $request, array &$options)
96
$method = $request->getMethod();
97
if ($method == 'HEAD') {
98
$options[CURLOPT_NOBODY] = true;
99
unset($options[CURLOPT_WRITEFUNCTION], $options[CURLOPT_READFUNCTION]);
101
$options[CURLOPT_CUSTOMREQUEST] = $method;
102
if (!$request->getBody()) {
103
unset($options[CURLOPT_READFUNCTION]);
105
$this->applyBody($request, $options);
110
private function applyBody(RequestInterface $request, array &$options)
112
if ($request->hasHeader('Content-Length')) {
113
$size = (int) $request->getHeader('Content-Length');
118
$request->getBody()->seek(0);
120
// You can send the body as a string using curl's CURLOPT_POSTFIELDS
121
if (($size !== null && $size < 32768) ||
122
isset($request->getConfig()['curl']['body_as_string'])
124
$options[CURLOPT_POSTFIELDS] = $request->getBody()->getContents();
125
// Don't duplicate the Content-Length header
126
$this->removeHeader('Content-Length', $options);
127
$this->removeHeader('Transfer-Encoding', $options);
129
$options[CURLOPT_UPLOAD] = true;
130
// Let cURL handle setting the Content-Length header
131
if ($size !== null) {
132
$options[CURLOPT_INFILESIZE] = $size;
133
$this->removeHeader('Content-Length', $options);
137
// If the Expect header is not present, prevent curl from adding it
138
if (!$request->hasHeader('Expect')) {
139
$options[CURLOPT_HTTPHEADER][] = 'Expect:';
143
private function applyHeaders(RequestInterface $request, array &$options)
145
foreach ($options['_headers'] as $name => $values) {
146
$options[CURLOPT_HTTPHEADER][] = $name . ': ' . implode(', ', $values);
149
// Remove the Expect header if one was not set
150
if (!$request->hasHeader('Accept')) {
151
$options[CURLOPT_HTTPHEADER][] = 'Accept:';
155
private function applyTransferOptions(
156
RequestInterface $request,
157
RequestMediator $mediator,
162
$methods = array_flip(get_class_methods(__CLASS__));
165
foreach ($request->getConfig()->toArray() as $key => $value) {
166
$method = "add_{$key}";
167
if (isset($methods[$method])) {
168
$this->{$method}($request, $mediator, $options, $value);
173
private function add_debug(
174
RequestInterface $request,
175
RequestMediator $mediator,
183
if (!is_resource($value)) {
184
$value = defined('STDOUT') ? STDOUT : fopen('php://output', 'w');
187
$options[CURLOPT_STDERR] = $value;
188
$options[CURLOPT_VERBOSE] = true;
191
private function add_proxy(
192
RequestInterface $request,
193
RequestMediator $mediator,
197
if (!is_array($value)) {
198
$options[CURLOPT_PROXY] = $value;
200
$scheme = $request->getScheme();
201
if (isset($value[$scheme])) {
202
$options[CURLOPT_PROXY] = $value[$scheme];
207
private function add_timeout(
208
RequestInterface $request,
209
RequestMediator $mediator,
213
$options[CURLOPT_TIMEOUT_MS] = $value * 1000;
216
private function add_connect_timeout(
217
RequestInterface $request,
218
RequestMediator $mediator,
222
$options[CURLOPT_CONNECTTIMEOUT_MS] = $value * 1000;
225
private function add_verify(
226
RequestInterface $request,
227
RequestMediator $mediator,
231
if ($value === false) {
232
unset($options[CURLOPT_CAINFO]);
233
$options[CURLOPT_SSL_VERIFYHOST] = 0;
234
$options[CURLOPT_SSL_VERIFYPEER] = false;
235
} elseif ($value === true || is_string($value)) {
236
$options[CURLOPT_SSL_VERIFYHOST] = 2;
237
$options[CURLOPT_SSL_VERIFYPEER] = true;
238
if ($value !== true) {
239
if (!file_exists($value)) {
240
throw new AdapterException('SSL certificate authority file'
241
. " not found: {$value}");
243
$options[CURLOPT_CAINFO] = $value;
248
private function add_cert(
249
RequestInterface $request,
250
RequestMediator $mediator,
254
if (!file_exists($value)) {
255
throw new AdapterException("SSL certificate not found: {$value}");
258
$options[CURLOPT_SSLCERT] = $value;
261
private function add_ssl_key(
262
RequestInterface $request,
263
RequestMediator $mediator,
267
if (is_array($value)) {
268
$options[CURLOPT_SSLKEYPASSWD] = $value[1];
272
if (!file_exists($value)) {
273
throw new AdapterException("SSL private key not found: {$value}");
276
$options[CURLOPT_SSLKEY] = $value;
279
private function add_stream(
280
RequestInterface $request,
281
RequestMediator $mediator,
285
if ($value === false) {
289
throw new AdapterException('cURL adapters do not support the "stream"'
290
. ' request option. This error is typically encountered when trying'
291
. ' to send requests with the "stream" option set to true in '
292
. ' parallel. You will either need to send these one at a time or'
293
. ' implement a custom ParallelAdapterInterface that supports'
294
. ' sending these types of requests in parallel. This error can'
295
. ' also occur if the StreamAdapter is not available on your'
296
. ' system (e.g., allow_url_fopen is disabled in your php.ini).');
299
private function add_save_to(
300
RequestInterface $request,
301
RequestMediator $mediator,
305
$mediator->setResponseBody(is_string($value)
306
? new LazyOpenStream($value, 'w')
307
: Stream::factory($value));
310
private function add_decode_content(
311
RequestInterface $request,
312
RequestMediator $mediator,
316
if (!$request->hasHeader('Accept-Encoding')) {
317
$options[CURLOPT_ENCODING] = '';
318
// Don't let curl send the header over the wire
319
$options[CURLOPT_HTTPHEADER][] = 'Accept-Encoding:';
321
$options[CURLOPT_ENCODING] = $request->getHeader('Accept-Encoding');
326
* Takes an array of curl options specified in the 'curl' option of a
327
* request's configuration array and maps them to CURLOPT_* options.
329
* This method is only called when a request has a 'curl' config setting.
330
* Array key strings that start with CURL that have a matching constant
331
* value will be automatically converted to the matching constant.
333
* @param array $config Configuration array of custom curl option
334
* @param array $options Array of existing curl options
336
* @return array Returns a new array of curl options
338
private function applyCustomCurlOptions(array $config, array $options)
340
unset($config['body_as_string']);
343
// Map curl constant strings to defined values
344
foreach ($config as $key => $value) {
345
if (defined($key) && substr($key, 0, 4) === 'CURL') {
346
$key = constant($key);
348
$curlOptions[$key] = $value;
351
return $curlOptions + $options;
355
* Remove a header from the options array
357
* @param string $name Case-insensitive header to remove
358
* @param array $options Array of options to modify
360
private function removeHeader($name, array &$options)
362
foreach (array_keys($options['_headers']) as $key) {
363
if (!strcasecmp($key, $name)) {
364
unset($options['_headers'][$key]);