scratch – Blame information for rev 87

Subversion Repositories:
Rev:
Rev Author Line No. Line
87 office 1 <?php
2 namespace GuzzleHttp\Adapter\Curl;
3  
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;
10  
11 /**
12 * Creates curl resources from a request and response object
13 */
14 class CurlFactory
15 {
16 /**
17 * Creates a cURL handle based on a transaction.
18 *
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
22 *
23 * @return resource Returns a prepared cURL handle
24 * @throws AdapterException when an option cannot be applied
25 */
26 public function __invoke(
27 TransactionInterface $transaction,
28 MessageFactoryInterface $messageFactory,
29 $handle = null
30 ) {
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']);
38  
39 // Add adapter options from the request's configuration options
40 if ($config = $request->getConfig()['curl']) {
41 $options = $this->applyCustomCurlOptions($config, $options);
42 }
43  
44 if (!$handle) {
45 $handle = curl_init();
46 }
47  
48 curl_setopt_array($handle, $options);
49  
50 return $handle;
51 }
52  
53 protected function getDefaultOptions(
54 RequestInterface $request,
55 RequestMediator $mediator
56 ) {
57 $url = $request->getUrl();
58  
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);
63 }
64  
65 $config = $request->getConfig();
66 $options = [
67 CURLOPT_URL => $url,
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()
79 ];
80  
81 if (defined('CURLOPT_PROTOCOLS')) {
82 // Allow only HTTP and HTTPS protocols
83 $options[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
84 }
85  
86 // cURL sometimes adds a content-type by default. Prevent this.
87 if (!$request->hasHeader('Content-Type')) {
88 $options[CURLOPT_HTTPHEADER][] = 'Content-Type:';
89 }
90  
91 return $options;
92 }
93  
94 private function applyMethod(RequestInterface $request, array &$options)
95 {
96 $method = $request->getMethod();
97 if ($method == 'HEAD') {
98 $options[CURLOPT_NOBODY] = true;
99 unset($options[CURLOPT_WRITEFUNCTION], $options[CURLOPT_READFUNCTION]);
100 } else {
101 $options[CURLOPT_CUSTOMREQUEST] = $method;
102 if (!$request->getBody()) {
103 unset($options[CURLOPT_READFUNCTION]);
104 } else {
105 $this->applyBody($request, $options);
106 }
107 }
108 }
109  
110 private function applyBody(RequestInterface $request, array &$options)
111 {
112 if ($request->hasHeader('Content-Length')) {
113 $size = (int) $request->getHeader('Content-Length');
114 } else {
115 $size = null;
116 }
117  
118 $request->getBody()->seek(0);
119  
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'])
123 ) {
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);
128 } else {
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);
134 }
135 }
136  
137 // If the Expect header is not present, prevent curl from adding it
138 if (!$request->hasHeader('Expect')) {
139 $options[CURLOPT_HTTPHEADER][] = 'Expect:';
140 }
141 }
142  
143 private function applyHeaders(RequestInterface $request, array &$options)
144 {
145 foreach ($options['_headers'] as $name => $values) {
146 $options[CURLOPT_HTTPHEADER][] = $name . ': ' . implode(', ', $values);
147 }
148  
149 // Remove the Expect header if one was not set
150 if (!$request->hasHeader('Accept')) {
151 $options[CURLOPT_HTTPHEADER][] = 'Accept:';
152 }
153 }
154  
155 private function applyTransferOptions(
156 RequestInterface $request,
157 RequestMediator $mediator,
158 array &$options
159 ) {
160 static $methods;
161 if (!$methods) {
162 $methods = array_flip(get_class_methods(__CLASS__));
163 }
164  
165 foreach ($request->getConfig()->toArray() as $key => $value) {
166 $method = "add_{$key}";
167 if (isset($methods[$method])) {
168 $this->{$method}($request, $mediator, $options, $value);
169 }
170 }
171 }
172  
173 private function add_debug(
174 RequestInterface $request,
175 RequestMediator $mediator,
176 &$options,
177 $value
178 ) {
179 if (!$value) {
180 return;
181 }
182  
183 if (!is_resource($value)) {
184 $value = defined('STDOUT') ? STDOUT : fopen('php://output', 'w');
185 }
186  
187 $options[CURLOPT_STDERR] = $value;
188 $options[CURLOPT_VERBOSE] = true;
189 }
190  
191 private function add_proxy(
192 RequestInterface $request,
193 RequestMediator $mediator,
194 &$options,
195 $value
196 ) {
197 if (!is_array($value)) {
198 $options[CURLOPT_PROXY] = $value;
199 } else {
200 $scheme = $request->getScheme();
201 if (isset($value[$scheme])) {
202 $options[CURLOPT_PROXY] = $value[$scheme];
203 }
204 }
205 }
206  
207 private function add_timeout(
208 RequestInterface $request,
209 RequestMediator $mediator,
210 &$options,
211 $value
212 ) {
213 $options[CURLOPT_TIMEOUT_MS] = $value * 1000;
214 }
215  
216 private function add_connect_timeout(
217 RequestInterface $request,
218 RequestMediator $mediator,
219 &$options,
220 $value
221 ) {
222 $options[CURLOPT_CONNECTTIMEOUT_MS] = $value * 1000;
223 }
224  
225 private function add_verify(
226 RequestInterface $request,
227 RequestMediator $mediator,
228 &$options,
229 $value
230 ) {
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}");
242 }
243 $options[CURLOPT_CAINFO] = $value;
244 }
245 }
246 }
247  
248 private function add_cert(
249 RequestInterface $request,
250 RequestMediator $mediator,
251 &$options,
252 $value
253 ) {
254 if (!file_exists($value)) {
255 throw new AdapterException("SSL certificate not found: {$value}");
256 }
257  
258 $options[CURLOPT_SSLCERT] = $value;
259 }
260  
261 private function add_ssl_key(
262 RequestInterface $request,
263 RequestMediator $mediator,
264 &$options,
265 $value
266 ) {
267 if (is_array($value)) {
268 $options[CURLOPT_SSLKEYPASSWD] = $value[1];
269 $value = $value[0];
270 }
271  
272 if (!file_exists($value)) {
273 throw new AdapterException("SSL private key not found: {$value}");
274 }
275  
276 $options[CURLOPT_SSLKEY] = $value;
277 }
278  
279 private function add_stream(
280 RequestInterface $request,
281 RequestMediator $mediator,
282 &$options,
283 $value
284 ) {
285 if ($value === false) {
286 return;
287 }
288  
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).');
297 }
298  
299 private function add_save_to(
300 RequestInterface $request,
301 RequestMediator $mediator,
302 &$options,
303 $value
304 ) {
305 $mediator->setResponseBody(is_string($value)
306 ? new LazyOpenStream($value, 'w')
307 : Stream::factory($value));
308 }
309  
310 private function add_decode_content(
311 RequestInterface $request,
312 RequestMediator $mediator,
313 &$options,
314 $value
315 ) {
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:';
320 } else {
321 $options[CURLOPT_ENCODING] = $request->getHeader('Accept-Encoding');
322 }
323 }
324  
325 /**
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.
328 *
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.
332 *
333 * @param array $config Configuration array of custom curl option
334 * @param array $options Array of existing curl options
335 *
336 * @return array Returns a new array of curl options
337 */
338 private function applyCustomCurlOptions(array $config, array $options)
339 {
340 unset($config['body_as_string']);
341 $curlOptions = [];
342  
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);
347 }
348 $curlOptions[$key] = $value;
349 }
350  
351 return $curlOptions + $options;
352 }
353  
354 /**
355 * Remove a header from the options array
356 *
357 * @param string $name Case-insensitive header to remove
358 * @param array $options Array of options to modify
359 */
360 private function removeHeader($name, array &$options)
361 {
362 foreach (array_keys($options['_headers']) as $key) {
363 if (!strcasecmp($key, $name)) {
364 unset($options['_headers'][$key]);
365 return;
366 }
367 }
368 }
369 }