Security update to Drupal 8.4.6
[yaffs-website] / vendor / guzzlehttp / guzzle / src / Handler / CurlMultiHandler.php
1 <?php
2 namespace GuzzleHttp\Handler;
3
4 use GuzzleHttp\Promise as P;
5 use GuzzleHttp\Promise\Promise;
6 use GuzzleHttp\Psr7;
7 use Psr\Http\Message\RequestInterface;
8
9 /**
10  * Returns an asynchronous response using curl_multi_* functions.
11  *
12  * When using the CurlMultiHandler, custom curl options can be specified as an
13  * associative array of curl option constants mapping to values in the
14  * **curl** key of the provided request options.
15  *
16  * @property resource $_mh Internal use only. Lazy loaded multi-handle.
17  */
18 class CurlMultiHandler
19 {
20     /** @var CurlFactoryInterface */
21     private $factory;
22     private $selectTimeout;
23     private $active;
24     private $handles = [];
25     private $delays = [];
26
27     /**
28      * This handler accepts the following options:
29      *
30      * - handle_factory: An optional factory  used to create curl handles
31      * - select_timeout: Optional timeout (in seconds) to block before timing
32      *   out while selecting curl handles. Defaults to 1 second.
33      *
34      * @param array $options
35      */
36     public function __construct(array $options = [])
37     {
38         $this->factory = isset($options['handle_factory'])
39             ? $options['handle_factory'] : new CurlFactory(50);
40         $this->selectTimeout = isset($options['select_timeout'])
41             ? $options['select_timeout'] : 1;
42     }
43
44     public function __get($name)
45     {
46         if ($name === '_mh') {
47             return $this->_mh = curl_multi_init();
48         }
49
50         throw new \BadMethodCallException();
51     }
52
53     public function __destruct()
54     {
55         if (isset($this->_mh)) {
56             curl_multi_close($this->_mh);
57             unset($this->_mh);
58         }
59     }
60
61     public function __invoke(RequestInterface $request, array $options)
62     {
63         $easy = $this->factory->create($request, $options);
64         $id = (int) $easy->handle;
65
66         $promise = new Promise(
67             [$this, 'execute'],
68             function () use ($id) {
69                 return $this->cancel($id);
70             }
71         );
72
73         $this->addRequest(['easy' => $easy, 'deferred' => $promise]);
74
75         return $promise;
76     }
77
78     /**
79      * Ticks the curl event loop.
80      */
81     public function tick()
82     {
83         // Add any delayed handles if needed.
84         if ($this->delays) {
85             $currentTime = microtime(true);
86             foreach ($this->delays as $id => $delay) {
87                 if ($currentTime >= $delay) {
88                     unset($this->delays[$id]);
89                     curl_multi_add_handle(
90                         $this->_mh,
91                         $this->handles[$id]['easy']->handle
92                     );
93                 }
94             }
95         }
96
97         // Step through the task queue which may add additional requests.
98         P\queue()->run();
99
100         if ($this->active &&
101             curl_multi_select($this->_mh, $this->selectTimeout) === -1
102         ) {
103             // Perform a usleep if a select returns -1.
104             // See: https://bugs.php.net/bug.php?id=61141
105             usleep(250);
106         }
107
108         while (curl_multi_exec($this->_mh, $this->active) === CURLM_CALL_MULTI_PERFORM);
109
110         $this->processMessages();
111     }
112
113     /**
114      * Runs until all outstanding connections have completed.
115      */
116     public function execute()
117     {
118         $queue = P\queue();
119
120         while ($this->handles || !$queue->isEmpty()) {
121             // If there are no transfers, then sleep for the next delay
122             if (!$this->active && $this->delays) {
123                 usleep($this->timeToNext());
124             }
125             $this->tick();
126         }
127     }
128
129     private function addRequest(array $entry)
130     {
131         $easy = $entry['easy'];
132         $id = (int) $easy->handle;
133         $this->handles[$id] = $entry;
134         if (empty($easy->options['delay'])) {
135             curl_multi_add_handle($this->_mh, $easy->handle);
136         } else {
137             $this->delays[$id] = microtime(true) + ($easy->options['delay'] / 1000);
138         }
139     }
140
141     /**
142      * Cancels a handle from sending and removes references to it.
143      *
144      * @param int $id Handle ID to cancel and remove.
145      *
146      * @return bool True on success, false on failure.
147      */
148     private function cancel($id)
149     {
150         // Cannot cancel if it has been processed.
151         if (!isset($this->handles[$id])) {
152             return false;
153         }
154
155         $handle = $this->handles[$id]['easy']->handle;
156         unset($this->delays[$id], $this->handles[$id]);
157         curl_multi_remove_handle($this->_mh, $handle);
158         curl_close($handle);
159
160         return true;
161     }
162
163     private function processMessages()
164     {
165         while ($done = curl_multi_info_read($this->_mh)) {
166             $id = (int) $done['handle'];
167             curl_multi_remove_handle($this->_mh, $done['handle']);
168
169             if (!isset($this->handles[$id])) {
170                 // Probably was cancelled.
171                 continue;
172             }
173
174             $entry = $this->handles[$id];
175             unset($this->handles[$id], $this->delays[$id]);
176             $entry['easy']->errno = $done['result'];
177             $entry['deferred']->resolve(
178                 CurlFactory::finish(
179                     $this,
180                     $entry['easy'],
181                     $this->factory
182                 )
183             );
184         }
185     }
186
187     private function timeToNext()
188     {
189         $currentTime = microtime(true);
190         $nextTime = PHP_INT_MAX;
191         foreach ($this->delays as $time) {
192             if ($time < $nextTime) {
193                 $nextTime = $time;
194             }
195         }
196
197         return max(0, $nextTime - $currentTime) * 1000000;
198     }
199 }