PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.1-beta.2
Jetpack – WP Security, Backup, Speed, & Growth v16.1-beta.2
16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / vendor / wp-php-toolkit / http-client / Transport / class-curltransport.php
jetpack / vendor / wp-php-toolkit / http-client / Transport Last commit date
class-curltransport.php 3 weeks ago class-sockettransport.php 3 weeks ago interface-transportinterface.php 3 weeks ago
class-curltransport.php
291 lines
1 <?php
2
3 namespace WordPress\HttpClient\Transport;
4
5 use WordPress\HttpClient\Client;
6 use WordPress\HttpClient\ClientState;
7 use WordPress\HttpClient\HttpClientException;
8 use WordPress\HttpClient\HttpError;
9 use WordPress\HttpClient\Request;
10 use WordPress\HttpClient\Response;
11
12 /**
13 * An HTTP client using curl multihandle.
14 *
15 * @extends Client
16 */
17 class CurlTransport implements TransportInterface {
18
19 /**
20 * @var ClientState
21 */
22 protected $state;
23
24 /**
25 * @var \CurlMultiHandle cURL multi-handle managing parallel requests
26 */
27 protected $multi_handle;
28
29 /**
30 * @var array Map of cURL handle resource IDs to request IDs (for callbacks).
31 */
32 protected $handle_map = array();
33
34 /**
35 * Initializes a new CurlClient with optional settings.
36 *
37 * @param ClientState $state The client state containing configuration.
38 */
39 public function __construct( ClientState $state ) {
40 $this->state = $state;
41
42 $this->multi_handle = curl_multi_init();
43 curl_multi_setopt( $this->multi_handle, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX );
44 curl_multi_setopt( $this->multi_handle, CURLMOPT_MAX_TOTAL_CONNECTIONS, $this->state->concurrency );
45 curl_multi_setopt( $this->multi_handle, CURLMOPT_MAX_HOST_CONNECTIONS, $this->state->concurrency );
46 }
47
48 /**
49 * Destructor to clean up the curl_multi handle.
50 */
51 public function __destruct() {
52 if ( $this->multi_handle ) {
53 curl_multi_close( $this->multi_handle );
54 $this->multi_handle = null;
55 }
56 }
57
58 public function event_loop_tick(): bool {
59 if ( 0 === count( $this->state->get_active_requests() ) ) {
60 return false;
61 }
62
63 $this->open_nonblocking_curl_handles(
64 $this->state->get_active_requests( array( Request::STATE_ENQUEUED ) )
65 );
66
67 if ( 0 === count( $this->handle_map ) ) {
68 return false;
69 }
70
71 $this->poll_active_curl_requests();
72
73 foreach ( $this->state->get_active_requests( array( Request::STATE_RECEIVED ) ) as $request ) {
74 $this->mark_finished( $request );
75 }
76
77 return true;
78 }
79
80 private function open_nonblocking_curl_handles( $requests ) {
81 foreach ( $requests as $request ) {
82 // Initialize and add the curl handle for this request.
83 $ch = $this->init_curl_handle( $request );
84 /** @var \CurlHandle $ch */
85 if ( ! $ch ) {
86 // If initialization fails, immediately mark this request as failed.
87 $this->set_error( $request, new HttpError( 'Failed to initialize cURL handle', $request ) );
88 continue;
89 }
90 if ( 0 !== curl_multi_add_handle( $this->multi_handle, $ch ) ) {
91 $this->set_error( $request, new HttpError( 'Failed to add cURL handle to multi handle', $request ) );
92 continue;
93 }
94 $this->state->connections[ $request->id ]->http_socket = $ch;
95 $this->handle_map[ (int) $ch ] = $request->id;
96 }
97 }
98
99 private function poll_active_curl_requests() {
100 $running = 0;
101 do {
102 $mrc = curl_multi_exec( $this->multi_handle, $running );
103 } while ( CURLM_CALL_MULTI_PERFORM === $mrc );
104
105 // Handle any completed requests.
106 while ( $info = curl_multi_info_read( $this->multi_handle ) ) {
107 if ( CURLMSG_DONE === $info['msg'] ) {
108 $ch = $info['handle'];
109 $id = $this->handle_map[ (int) $ch ] ?? null;
110 if ( null === $id ) {
111 throw new HttpClientException( 'Received completion event for an unknown request ' . ( $ch ? (int) $ch : 'unknown' ) );
112 }
113 $request = $this->state->get_request_by_id( $id );
114 if ( CURLE_OK !== $info['result'] ) {
115 $this->set_error( $request, new HttpError( sprintf( 'cURL error %d: %s', $info['result'], curl_error( $ch ) ) ) );
116 return;
117 }
118 if ( ! $request->response ) {
119 $this->set_error( $request, new HttpError( 'Connection closed while reading response headers.', $request ) );
120 return;
121 }
122 if ( Request::STATE_FAILED === $request->state || Request::STATE_FINISHED === $request->state ) {
123 // We've already handled errors and successes.
124 continue;
125 }
126 // We'll mark it as finished in the event_loop_tick() method.
127 $request->state = Request::STATE_RECEIVED;
128 }
129 }
130
131 // @TODO: What kind of timeout should we use here?
132 curl_multi_select( $this->multi_handle, 0.05 );
133 }
134
135 /**
136 * Create and configure a curl handle for the given Request.
137 *
138 * @param Request $request The HTTP request to prepare.
139 * @return resource|false Returns the configured cURL handle, or false on failure.
140 */
141 private function init_curl_handle( $request ) {
142 $ch = curl_init();
143 if ( ! $ch ) {
144 throw new HttpClientException( 'Failed to initialize cURL handle' );
145 }
146 // Basic curl settings for the request.
147 curl_setopt( $ch, CURLOPT_URL, $request->url );
148 curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, false );
149 // Redirects are handled in the Client.
150 curl_setopt( $ch, CURLOPT_MAXREDIRS, 0 );
151 curl_setopt( $ch, CURLOPT_TIMEOUT_MS, $this->state->request_timeout_ms );
152 curl_setopt( $ch, CURLOPT_RETURNTRANSFER, false ); // use callbacks for data.
153 curl_setopt( $ch, CURLOPT_HEADER, false ); // headers via callback.
154 curl_setopt( $ch, CURLOPT_ENCODING, '' );
155 // Set HTTP method and body if needed.
156 curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, $request->method );
157 if ( ! empty( $request->upload_body_stream ) ) {
158 curl_setopt( $ch, CURLOPT_UPLOAD, true );
159 curl_setopt(
160 $ch,
161 CURLOPT_READFUNCTION,
162 function ( $ch, $fp, $length ) use ( $request ) {
163 $stream = $request->upload_body_stream;
164 // Pull at most $length bytes until we either get some bytes
165 // or we reach the end of the stream.
166 while ( ! $stream->reached_end_of_data() ) {
167 $got_bytes = $stream->pull( $length );
168 if ( $got_bytes > 0 ) {
169 return $stream->consume( $got_bytes );
170 }
171 }
172 return '';
173 }
174 );
175 }
176 // Set headers if provided.
177 if ( ! empty( $request->headers ) ) {
178 $header_lines = array();
179 foreach ( $request->headers as $name => $value ) {
180 $header_lines[] = "{$name}: {$value}";
181 if ( 'content-length' === $name && is_numeric( $value ) ) {
182 curl_setopt( $ch, CURLOPT_INFILESIZE, (int) $value );
183 }
184 }
185 curl_setopt( $ch, CURLOPT_HTTPHEADER, $header_lines );
186 }
187 // Set callback functions for data and headers.
188 curl_setopt(
189 $ch,
190 CURLOPT_WRITEFUNCTION,
191 function ( $ch, $data ) {
192 return $this->handle_body_data( $ch, $data );
193 }
194 );
195 curl_setopt(
196 $ch,
197 CURLOPT_HEADERFUNCTION,
198 function ( $ch, $header ) {
199 return $this->handle_header_line( $ch, $header );
200 }
201 );
202 // Disable signals (required for timeout in multi).
203 curl_setopt( $ch, CURLOPT_NOSIGNAL, true );
204 $request->state = Request::STATE_WILL_SEND_HEADERS;
205
206 return $ch;
207 }
208
209 /**
210 * `cURL` callback to handle incoming header lines.
211 * Triggers an EVENT_GOT_HEADERS event when the header section is complete.
212 *
213 * @param resource $ch The cURL handle.
214 * @param string $header_line A line from the response headers.
215 * @return int Number of bytes handled (required by cURL).
216 */
217 private function handle_header_line( $ch, $header_line ) {
218 $request = $this->get_request_by_handle( $ch );
219 if ( null === $request ) {
220 throw new HttpClientException( 'Received header data for an unknown request ' . ( $ch ? (int) $ch : 'unknown' ) );
221 }
222 $connection = $this->state->connections[ $request->id ];
223 if ( 0 === strlen( $connection->response_buffer ) ) {
224 $request->state = Request::STATE_RECEIVING_HEADERS;
225 }
226 $connection->response_buffer .= $header_line;
227
228 // Check for the end of the header section.
229 if ( '' === trim( $header_line ) ) {
230 $request->response = Response::from_http_headers(
231 $connection->response_buffer,
232 $request
233 );
234 $connection->response_buffer = '';
235 if ( false === $request->response ) {
236 $request->response = new Response( $request );
237 $this->set_error( $request, new HttpError( 'Failed to parse headers', $request ) );
238 return strlen( $header_line );
239 }
240 $this->state->events[ $request->id ][ Client::EVENT_GOT_HEADERS ] = true;
241 $request->state = Request::STATE_RECEIVING_BODY;
242 return strlen( $header_line );
243 }
244
245 return strlen( $header_line );
246 }
247
248 /**
249 * `cURL` callback to handle chunks of response body data.
250 * Triggers an EVENT_BODY_CHUNK_AVAILABLE event for each chunk received.
251 *
252 * @param resource $ch The cURL handle.
253 * @param string $data The chunk of response body data.
254 * @return int Number of bytes handled.
255 */
256 private function handle_body_data( $ch, $data ) {
257 $request = $this->get_request_by_handle( $ch );
258 if ( null === $request ) {
259 throw new HttpClientException( 'Received body data for an unknown request ' . ( $ch ? (int) $ch : 'unknown' ) );
260 }
261 $this->state->connections[ $request->id ]->response_buffer .= $data;
262 $this->state->events[ $request->id ][ Client::EVENT_BODY_CHUNK_AVAILABLE ] = true;
263
264 return strlen( $data );
265 }
266
267 private function get_request_by_handle( $handle ) {
268 $request_id = $this->handle_map[ (int) $handle ] ?? null;
269 return $this->state->get_request_by_id( $request_id );
270 }
271
272 private function mark_finished( Request $request ) {
273 $this->state->set_request_finished( $request );
274 $this->close_connection( $request );
275 }
276
277 private function set_error( Request $request, $error ) {
278 $this->state->set_request_error( $request, $error );
279 $this->close_connection( $request );
280 }
281
282 private function close_connection( Request $request ) {
283 $handle = $this->state->connections[ $request->id ]->http_socket;
284 if ( null !== $handle ) {
285 curl_multi_remove_handle( $this->multi_handle, $handle );
286 curl_close( $handle );
287 }
288 unset( $this->handle_map[ (int) $handle ] );
289 }
290 }
291