Verzeichnisstruktur phpBB-3.3.15
- Veröffentlicht
- 28.08.2024
So funktioniert es
|
Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück |
Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis. |
|
(Beispiel Datei-Icons)
|
Auf das Icon klicken um den Quellcode anzuzeigen |
CurlFactory.php
001 <?php
002 namespace GuzzleHttp\Handler;
003
004 use GuzzleHttp\Exception\ConnectException;
005 use GuzzleHttp\Exception\RequestException;
006 use GuzzleHttp\Promise\FulfilledPromise;
007 use GuzzleHttp\Psr7;
008 use GuzzleHttp\Psr7\LazyOpenStream;
009 use GuzzleHttp\TransferStats;
010 use Psr\Http\Message\RequestInterface;
011
012 /**
013 * Creates curl resources from a request
014 */
015 class CurlFactory implements CurlFactoryInterface
016 {
017 const CURL_VERSION_STR = 'curl_version';
018 const LOW_CURL_VERSION_NUMBER = '7.21.2';
019
020 /** @var array */
021 private $handles = [];
022
023 /** @var int Total number of idle handles to keep in cache */
024 private $maxHandles;
025
026 /**
027 * @param int $maxHandles Maximum number of idle handles.
028 */
029 public function __construct($maxHandles)
030 {
031 $this->maxHandles = $maxHandles;
032 }
033
034 public function create(RequestInterface $request, array $options)
035 {
036 if (isset($options['curl']['body_as_string'])) {
037 $options['_body_as_string'] = $options['curl']['body_as_string'];
038 unset($options['curl']['body_as_string']);
039 }
040
041 $easy = new EasyHandle;
042 $easy->request = $request;
043 $easy->options = $options;
044 $conf = $this->getDefaultConf($easy);
045 $this->applyMethod($easy, $conf);
046 $this->applyHandlerOptions($easy, $conf);
047 $this->applyHeaders($easy, $conf);
048 unset($conf['_headers']);
049
050 // Add handler options from the request configuration options
051 if (isset($options['curl'])) {
052 $conf = array_replace($conf, $options['curl']);
053 }
054
055 $conf[CURLOPT_HEADERFUNCTION] = $this->createHeaderFn($easy);
056 $easy->handle = $this->handles
057 ? array_pop($this->handles)
058 : curl_init();
059 curl_setopt_array($easy->handle, $conf);
060
061 return $easy;
062 }
063
064 public function release(EasyHandle $easy)
065 {
066 $resource = $easy->handle;
067 unset($easy->handle);
068
069 if (count($this->handles) >= $this->maxHandles) {
070 curl_close($resource);
071 } else {
072 // Remove all callback functions as they can hold onto references
073 // and are not cleaned up by curl_reset. Using curl_setopt_array
074 // does not work for some reason, so removing each one
075 // individually.
076 curl_setopt($resource, CURLOPT_HEADERFUNCTION, null);
077 curl_setopt($resource, CURLOPT_READFUNCTION, null);
078 curl_setopt($resource, CURLOPT_WRITEFUNCTION, null);
079 curl_setopt($resource, CURLOPT_PROGRESSFUNCTION, null);
080 curl_reset($resource);
081 $this->handles[] = $resource;
082 }
083 }
084
085 /**
086 * Completes a cURL transaction, either returning a response promise or a
087 * rejected promise.
088 *
089 * @param callable $handler
090 * @param EasyHandle $easy
091 * @param CurlFactoryInterface $factory Dictates how the handle is released
092 *
093 * @return \GuzzleHttp\Promise\PromiseInterface
094 */
095 public static function finish(
096 callable $handler,
097 EasyHandle $easy,
098 CurlFactoryInterface $factory
099 ) {
100 if (isset($easy->options['on_stats'])) {
101 self::invokeStats($easy);
102 }
103
104 if (!$easy->response || $easy->errno) {
105 return self::finishError($handler, $easy, $factory);
106 }
107
108 // Return the response if it is present and there is no error.
109 $factory->release($easy);
110
111 // Rewind the body of the response if possible.
112 $body = $easy->response->getBody();
113 if ($body->isSeekable()) {
114 $body->rewind();
115 }
116
117 return new FulfilledPromise($easy->response);
118 }
119
120 private static function invokeStats(EasyHandle $easy)
121 {
122 $curlStats = curl_getinfo($easy->handle);
123 $curlStats['appconnect_time'] = curl_getinfo($easy->handle, CURLINFO_APPCONNECT_TIME);
124 $stats = new TransferStats(
125 $easy->request,
126 $easy->response,
127 $curlStats['total_time'],
128 $easy->errno,
129 $curlStats
130 );
131 call_user_func($easy->options['on_stats'], $stats);
132 }
133
134 private static function finishError(
135 callable $handler,
136 EasyHandle $easy,
137 CurlFactoryInterface $factory
138 ) {
139 // Get error information and release the handle to the factory.
140 $ctx = [
141 'errno' => $easy->errno,
142 'error' => curl_error($easy->handle),
143 'appconnect_time' => curl_getinfo($easy->handle, CURLINFO_APPCONNECT_TIME),
144 ] + curl_getinfo($easy->handle);
145 $ctx[self::CURL_VERSION_STR] = curl_version()['version'];
146 $factory->release($easy);
147
148 // Retry when nothing is present or when curl failed to rewind.
149 if (empty($easy->options['_err_message'])
150 && (!$easy->errno || $easy->errno == 65)
151 ) {
152 return self::retryFailedRewind($handler, $easy, $ctx);
153 }
154
155 return self::createRejection($easy, $ctx);
156 }
157
158 private static function createRejection(EasyHandle $easy, array $ctx)
159 {
160 static $connectionErrors = [
161 CURLE_OPERATION_TIMEOUTED => true,
162 CURLE_COULDNT_RESOLVE_HOST => true,
163 CURLE_COULDNT_CONNECT => true,
164 CURLE_SSL_CONNECT_ERROR => true,
165 CURLE_GOT_NOTHING => true,
166 ];
167
168 // If an exception was encountered during the onHeaders event, then
169 // return a rejected promise that wraps that exception.
170 if ($easy->onHeadersException) {
171 return \GuzzleHttp\Promise\rejection_for(
172 new RequestException(
173 'An error was encountered during the on_headers event',
174 $easy->request,
175 $easy->response,
176 $easy->onHeadersException,
177 $ctx
178 )
179 );
180 }
181 if (version_compare($ctx[self::CURL_VERSION_STR], self::LOW_CURL_VERSION_NUMBER)) {
182 $message = sprintf(
183 'cURL error %s: %s (%s)',
184 $ctx['errno'],
185 $ctx['error'],
186 'see https://curl.haxx.se/libcurl/c/libcurl-errors.html'
187 );
188 } else {
189 $message = sprintf(
190 'cURL error %s: %s (%s) for %s',
191 $ctx['errno'],
192 $ctx['error'],
193 'see https://curl.haxx.se/libcurl/c/libcurl-errors.html',
194 $easy->request->getUri()
195 );
196 }
197
198 // Create a connection exception if it was a specific error code.
199 $error = isset($connectionErrors[$easy->errno])
200 ? new ConnectException($message, $easy->request, null, $ctx)
201 : new RequestException($message, $easy->request, $easy->response, null, $ctx);
202
203 return \GuzzleHttp\Promise\rejection_for($error);
204 }
205
206 private function getDefaultConf(EasyHandle $easy)
207 {
208 $conf = [
209 '_headers' => $easy->request->getHeaders(),
210 CURLOPT_CUSTOMREQUEST => $easy->request->getMethod(),
211 CURLOPT_URL => (string) $easy->request->getUri()->withFragment(''),
212 CURLOPT_RETURNTRANSFER => false,
213 CURLOPT_HEADER => false,
214 CURLOPT_CONNECTTIMEOUT => 150,
215 ];
216
217 if (defined('CURLOPT_PROTOCOLS')) {
218 $conf[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
219 }
220
221 $version = $easy->request->getProtocolVersion();
222 if ($version == 1.1) {
223 $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1;
224 } elseif ($version == 2.0) {
225 $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2_0;
226 } else {
227 $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0;
228 }
229
230 return $conf;
231 }
232
233 private function applyMethod(EasyHandle $easy, array &$conf)
234 {
235 $body = $easy->request->getBody();
236 $size = $body->getSize();
237
238 if ($size === null || $size > 0) {
239 $this->applyBody($easy->request, $easy->options, $conf);
240 return;
241 }
242
243 $method = $easy->request->getMethod();
244 if ($method === 'PUT' || $method === 'POST') {
245 // See http://tools.ietf.org/html/rfc7230#section-3.3.2
246 if (!$easy->request->hasHeader('Content-Length')) {
247 $conf[CURLOPT_HTTPHEADER][] = 'Content-Length: 0';
248 }
249 } elseif ($method === 'HEAD') {
250 $conf[CURLOPT_NOBODY] = true;
251 unset(
252 $conf[CURLOPT_WRITEFUNCTION],
253 $conf[CURLOPT_READFUNCTION],
254 $conf[CURLOPT_FILE],
255 $conf[CURLOPT_INFILE]
256 );
257 }
258 }
259
260 private function applyBody(RequestInterface $request, array $options, array &$conf)
261 {
262 $size = $request->hasHeader('Content-Length')
263 ? (int) $request->getHeaderLine('Content-Length')
264 : null;
265
266 // Send the body as a string if the size is less than 1MB OR if the
267 // [curl][body_as_string] request value is set.
268 if (($size !== null && $size < 1000000) ||
269 !empty($options['_body_as_string'])
270 ) {
271 $conf[CURLOPT_POSTFIELDS] = (string) $request->getBody();
272 // Don't duplicate the Content-Length header
273 $this->removeHeader('Content-Length', $conf);
274 $this->removeHeader('Transfer-Encoding', $conf);
275 } else {
276 $conf[CURLOPT_UPLOAD] = true;
277 if ($size !== null) {
278 $conf[CURLOPT_INFILESIZE] = $size;
279 $this->removeHeader('Content-Length', $conf);
280 }
281 $body = $request->getBody();
282 if ($body->isSeekable()) {
283 $body->rewind();
284 }
285 $conf[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body) {
286 return $body->read($length);
287 };
288 }
289
290 // If the Expect header is not present, prevent curl from adding it
291 if (!$request->hasHeader('Expect')) {
292 $conf[CURLOPT_HTTPHEADER][] = 'Expect:';
293 }
294
295 // cURL sometimes adds a content-type by default. Prevent this.
296 if (!$request->hasHeader('Content-Type')) {
297 $conf[CURLOPT_HTTPHEADER][] = 'Content-Type:';
298 }
299 }
300
301 private function applyHeaders(EasyHandle $easy, array &$conf)
302 {
303 foreach ($conf['_headers'] as $name => $values) {
304 foreach ($values as $value) {
305 $value = (string) $value;
306 if ($value === '') {
307 // cURL requires a special format for empty headers.
308 // See https://github.com/guzzle/guzzle/issues/1882 for more details.
309 $conf[CURLOPT_HTTPHEADER][] = "$name;";
310 } else {
311 $conf[CURLOPT_HTTPHEADER][] = "$name: $value";
312 }
313 }
314 }
315
316 // Remove the Accept header if one was not set
317 if (!$easy->request->hasHeader('Accept')) {
318 $conf[CURLOPT_HTTPHEADER][] = 'Accept:';
319 }
320 }
321
322 /**
323 * Remove a header from the options array.
324 *
325 * @param string $name Case-insensitive header to remove
326 * @param array $options Array of options to modify
327 */
328 private function removeHeader($name, array &$options)
329 {
330 foreach (array_keys($options['_headers']) as $key) {
331 if (!strcasecmp($key, $name)) {
332 unset($options['_headers'][$key]);
333 return;
334 }
335 }
336 }
337
338 private function applyHandlerOptions(EasyHandle $easy, array &$conf)
339 {
340 $options = $easy->options;
341 if (isset($options['verify'])) {
342 if ($options['verify'] === false) {
343 unset($conf[CURLOPT_CAINFO]);
344 $conf[CURLOPT_SSL_VERIFYHOST] = 0;
345 $conf[CURLOPT_SSL_VERIFYPEER] = false;
346 } else {
347 $conf[CURLOPT_SSL_VERIFYHOST] = 2;
348 $conf[CURLOPT_SSL_VERIFYPEER] = true;
349 if (is_string($options['verify'])) {
350 // Throw an error if the file/folder/link path is not valid or doesn't exist.
351 if (!file_exists($options['verify'])) {
352 throw new \InvalidArgumentException(
353 "SSL CA bundle not found: {$options['verify']}"
354 );
355 }
356 // If it's a directory or a link to a directory use CURLOPT_CAPATH.
357 // If not, it's probably a file, or a link to a file, so use CURLOPT_CAINFO.
358 if (is_dir($options['verify']) ||
359 (is_link($options['verify']) && is_dir(readlink($options['verify'])))) {
360 $conf[CURLOPT_CAPATH] = $options['verify'];
361 } else {
362 $conf[CURLOPT_CAINFO] = $options['verify'];
363 }
364 }
365 }
366 }
367
368 if (!empty($options['decode_content'])) {
369 $accept = $easy->request->getHeaderLine('Accept-Encoding');
370 if ($accept) {
371 $conf[CURLOPT_ENCODING] = $accept;
372 } else {
373 $conf[CURLOPT_ENCODING] = '';
374 // Don't let curl send the header over the wire
375 $conf[CURLOPT_HTTPHEADER][] = 'Accept-Encoding:';
376 }
377 }
378
379 if (isset($options['sink'])) {
380 $sink = $options['sink'];
381 if (!is_string($sink)) {
382 $sink = \GuzzleHttp\Psr7\stream_for($sink);
383 } elseif (!is_dir(dirname($sink))) {
384 // Ensure that the directory exists before failing in curl.
385 throw new \RuntimeException(sprintf(
386 'Directory %s does not exist for sink value of %s',
387 dirname($sink),
388 $sink
389 ));
390 } else {
391 $sink = new LazyOpenStream($sink, 'w+');
392 }
393 $easy->sink = $sink;
394 $conf[CURLOPT_WRITEFUNCTION] = function ($ch, $write) use ($sink) {
395 return $sink->write($write);
396 };
397 } else {
398 // Use a default temp stream if no sink was set.
399 $conf[CURLOPT_FILE] = fopen('php://temp', 'w+');
400 $easy->sink = Psr7\stream_for($conf[CURLOPT_FILE]);
401 }
402 $timeoutRequiresNoSignal = false;
403 if (isset($options['timeout'])) {
404 $timeoutRequiresNoSignal |= $options['timeout'] < 1;
405 $conf[CURLOPT_TIMEOUT_MS] = $options['timeout'] * 1000;
406 }
407
408 // CURL default value is CURL_IPRESOLVE_WHATEVER
409 if (isset($options['force_ip_resolve'])) {
410 if ('v4' === $options['force_ip_resolve']) {
411 $conf[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V4;
412 } elseif ('v6' === $options['force_ip_resolve']) {
413 $conf[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V6;
414 }
415 }
416
417 if (isset($options['connect_timeout'])) {
418 $timeoutRequiresNoSignal |= $options['connect_timeout'] < 1;
419 $conf[CURLOPT_CONNECTTIMEOUT_MS] = $options['connect_timeout'] * 1000;
420 }
421
422 if ($timeoutRequiresNoSignal && strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
423 $conf[CURLOPT_NOSIGNAL] = true;
424 }
425
426 if (isset($options['proxy'])) {
427 if (!is_array($options['proxy'])) {
428 $conf[CURLOPT_PROXY] = $options['proxy'];
429 } else {
430 $scheme = $easy->request->getUri()->getScheme();
431 if (isset($options['proxy'][$scheme])) {
432 $host = $easy->request->getUri()->getHost();
433 if (!isset($options['proxy']['no']) ||
434 !\GuzzleHttp\is_host_in_noproxy($host, $options['proxy']['no'])
435 ) {
436 $conf[CURLOPT_PROXY] = $options['proxy'][$scheme];
437 }
438 }
439 }
440 }
441
442 if (isset($options['cert'])) {
443 $cert = $options['cert'];
444 if (is_array($cert)) {
445 $conf[CURLOPT_SSLCERTPASSWD] = $cert[1];
446 $cert = $cert[0];
447 }
448 if (!file_exists($cert)) {
449 throw new \InvalidArgumentException(
450 "SSL certificate not found: {$cert}"
451 );
452 }
453 $conf[CURLOPT_SSLCERT] = $cert;
454 }
455
456 if (isset($options['ssl_key'])) {
457 if (is_array($options['ssl_key'])) {
458 if (count($options['ssl_key']) === 2) {
459 list($sslKey, $conf[CURLOPT_SSLKEYPASSWD]) = $options['ssl_key'];
460 } else {
461 list($sslKey) = $options['ssl_key'];
462 }
463 }
464
465 $sslKey = isset($sslKey) ? $sslKey: $options['ssl_key'];
466
467 if (!file_exists($sslKey)) {
468 throw new \InvalidArgumentException(
469 "SSL private key not found: {$sslKey}"
470 );
471 }
472 $conf[CURLOPT_SSLKEY] = $sslKey;
473 }
474
475 if (isset($options['progress'])) {
476 $progress = $options['progress'];
477 if (!is_callable($progress)) {
478 throw new \InvalidArgumentException(
479 'progress client option must be callable'
480 );
481 }
482 $conf[CURLOPT_NOPROGRESS] = false;
483 $conf[CURLOPT_PROGRESSFUNCTION] = function () use ($progress) {
484 $args = func_get_args();
485 // PHP 5.5 pushed the handle onto the start of the args
486 if (is_resource($args[0])) {
487 array_shift($args);
488 }
489 call_user_func_array($progress, $args);
490 };
491 }
492
493 if (!empty($options['debug'])) {
494 $conf[CURLOPT_STDERR] = \GuzzleHttp\debug_resource($options['debug']);
495 $conf[CURLOPT_VERBOSE] = true;
496 }
497 }
498
499 /**
500 * This function ensures that a response was set on a transaction. If one
501 * was not set, then the request is retried if possible. This error
502 * typically means you are sending a payload, curl encountered a
503 * "Connection died, retrying a fresh connect" error, tried to rewind the
504 * stream, and then encountered a "necessary data rewind wasn't possible"
505 * error, causing the request to be sent through curl_multi_info_read()
506 * without an error status.
507 */
508 private static function retryFailedRewind(
509 callable $handler,
510 EasyHandle $easy,
511 array $ctx
512 ) {
513 try {
514 // Only rewind if the body has been read from.
515 $body = $easy->request->getBody();
516 if ($body->tell() > 0) {
517 $body->rewind();
518 }
519 } catch (\RuntimeException $e) {
520 $ctx['error'] = 'The connection unexpectedly failed without '
521 . 'providing an error. The request would have been retried, '
522 . 'but attempting to rewind the request body failed. '
523 . 'Exception: ' . $e;
524 return self::createRejection($easy, $ctx);
525 }
526
527 // Retry no more than 3 times before giving up.
528 if (!isset($easy->options['_curl_retries'])) {
529 $easy->options['_curl_retries'] = 1;
530 } elseif ($easy->options['_curl_retries'] == 2) {
531 $ctx['error'] = 'The cURL request was retried 3 times '
532 . 'and did not succeed. The most likely reason for the failure '
533 . 'is that cURL was unable to rewind the body of the request '
534 . 'and subsequent retries resulted in the same error. Turn on '
535 . 'the debug option to see what went wrong. See '
536 . 'https://bugs.php.net/bug.php?id=47204 for more information.';
537 return self::createRejection($easy, $ctx);
538 } else {
539 $easy->options['_curl_retries']++;
540 }
541
542 return $handler($easy->request, $easy->options);
543 }
544
545 private function createHeaderFn(EasyHandle $easy)
546 {
547 if (isset($easy->options['on_headers'])) {
548 $onHeaders = $easy->options['on_headers'];
549
550 if (!is_callable($onHeaders)) {
551 throw new \InvalidArgumentException('on_headers must be callable');
552 }
553 } else {
554 $onHeaders = null;
555 }
556
557 return function ($ch, $h) use (
558 $onHeaders,
559 $easy,
560 &$startingResponse
561 ) {
562 $value = trim($h);
563 if ($value === '') {
564 $startingResponse = true;
565 $easy->createResponse();
566 if ($onHeaders !== null) {
567 try {
568 $onHeaders($easy->response);
569 } catch (\Exception $e) {
570 // Associate the exception with the handle and trigger
571 // a curl header write error by returning 0.
572 $easy->onHeadersException = $e;
573 return -1;
574 }
575 }
576 } elseif ($startingResponse) {
577 $startingResponse = false;
578 $easy->headers = [$value];
579 } else {
580 $easy->headers[] = $value;
581 }
582 return strlen($h);
583 };
584 }
585 }
586