Completed
Push — master ( 3e1ca6...29c73f )
by Mark
47s
created

Curl::getProtocolVersion()   B

Complexity

Conditions 7
Paths 9

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
nc 9
nop 1
dl 0
loc 20
rs 8.6666
c 0
b 0
f 0
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