xref: /PHP-7.0/ext/curl/multi.c (revision 478f119a)
1 /*
2    +----------------------------------------------------------------------+
3    | PHP Version 7                                                        |
4    +----------------------------------------------------------------------+
5    | Copyright (c) 1997-2017 The PHP Group                                |
6    +----------------------------------------------------------------------+
7    | This source file is subject to version 3.01 of the PHP license,      |
8    | that is bundled with this package in the file LICENSE, and is        |
9    | available through the world-wide-web at the following url:           |
10    | http://www.php.net/license/3_01.txt                                  |
11    | If you did not receive a copy of the PHP license and are unable to   |
12    | obtain it through the world-wide-web, please send a note to          |
13    | license@php.net so we can mail you a copy immediately.               |
14    +----------------------------------------------------------------------+
15    | Author: Sterling Hughes <sterling@php.net>                           |
16    +----------------------------------------------------------------------+
17 */
18 
19 /* $Id$ */
20 
21 #define ZEND_INCLUDE_FULL_WINDOWS_HEADERS
22 
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26 
27 #include "php.h"
28 
29 #if HAVE_CURL
30 
31 #include "php_curl.h"
32 
33 #include <curl/curl.h>
34 #include <curl/multi.h>
35 
36 #ifdef HAVE_SYS_SELECT_H
37 #include <sys/select.h>
38 #endif
39 
40 #ifdef HAVE_SYS_TIME_H
41 #include <sys/time.h>
42 #endif
43 
44 #ifdef HAVE_SYS_TYPES_H
45 #include <sys/types.h>
46 #endif
47 
48 #ifdef HAVE_UNISTD_H
49 #include <unistd.h>
50 #endif
51 
52 /* {{{ proto resource curl_multi_init(void)
53    Returns a new cURL multi handle */
PHP_FUNCTION(curl_multi_init)54 PHP_FUNCTION(curl_multi_init)
55 {
56 	php_curlm *mh;
57 
58 	if (zend_parse_parameters_none() == FAILURE) {
59 		return;
60 	}
61 
62 	mh = ecalloc(1, sizeof(php_curlm));
63 	mh->multi = curl_multi_init();
64 
65 	zend_llist_init(&mh->easyh, sizeof(zval), _php_curl_multi_cleanup_list, 0);
66 
67 	RETURN_RES(zend_register_resource(mh, le_curl_multi_handle));
68 }
69 /* }}} */
70 
71 /* {{{ proto int curl_multi_add_handle(resource mh, resource ch)
72    Add a normal cURL handle to a cURL multi handle */
PHP_FUNCTION(curl_multi_add_handle)73 PHP_FUNCTION(curl_multi_add_handle)
74 {
75 	zval      *z_mh;
76 	zval      *z_ch;
77 	php_curlm *mh;
78 	php_curl  *ch;
79 	zval tmp_val;
80 
81 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "rr", &z_mh, &z_ch) == FAILURE) {
82 		return;
83 	}
84 
85 	if ((mh = (php_curlm *)zend_fetch_resource(Z_RES_P(z_mh), le_curl_multi_handle_name, le_curl_multi_handle)) == NULL) {
86 		RETURN_FALSE;
87 	}
88 
89 	if ((ch = (php_curl *)zend_fetch_resource(Z_RES_P(z_ch), le_curl_name, le_curl)) == NULL) {
90 		RETURN_FALSE;
91 	}
92 
93 	_php_curl_cleanup_handle(ch);
94 
95 	/* we want to create a copy of this zval that we store in the multihandle structure element "easyh" */
96 	ZVAL_DUP(&tmp_val, z_ch);
97 
98 	zend_llist_add_element(&mh->easyh, &tmp_val);
99 
100 	RETURN_LONG((zend_long)curl_multi_add_handle(mh->multi, ch->cp));
101 }
102 /* }}} */
103 
_php_curl_multi_cleanup_list(void * data)104 void _php_curl_multi_cleanup_list(void *data) /* {{{ */
105 {
106 	zval *z_ch = (zval *)data;
107 	php_curl *ch;
108 
109 	if (!z_ch) {
110 		return;
111 	}
112 	if (!Z_RES_P(z_ch)->ptr) {
113 		return;
114 	}
115 	if ((ch = (php_curl *)zend_fetch_resource(Z_RES_P(z_ch), le_curl_name, le_curl)) == NULL) {
116 		return;
117 	}
118 
119 	zend_list_delete(Z_RES_P(z_ch));
120 }
121 /* }}} */
122 
123 /* Used internally as comparison routine passed to zend_list_del_element */
curl_compare_resources(zval * z1,zval * z2)124 static int curl_compare_resources( zval *z1, zval *z2 ) /* {{{ */
125 {
126 	return (Z_TYPE_P(z1) == Z_TYPE_P(z2) &&
127 			Z_TYPE_P(z1) == IS_RESOURCE &&
128 			Z_RES_P(z1) == Z_RES_P(z2));
129 }
130 /* }}} */
131 
132 /* {{{ proto int curl_multi_remove_handle(resource mh, resource ch)
133    Remove a multi handle from a set of cURL handles */
PHP_FUNCTION(curl_multi_remove_handle)134 PHP_FUNCTION(curl_multi_remove_handle)
135 {
136 	zval      *z_mh;
137 	zval      *z_ch;
138 	php_curlm *mh;
139 	php_curl  *ch;
140 
141 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "rr", &z_mh, &z_ch) == FAILURE) {
142 		return;
143 	}
144 
145 	if ((mh = (php_curlm *)zend_fetch_resource(Z_RES_P(z_mh), le_curl_multi_handle_name, le_curl_multi_handle)) == NULL) {
146 		RETURN_FALSE;
147 	}
148 
149 	if ((ch = (php_curl *)zend_fetch_resource(Z_RES_P(z_ch), le_curl_name, le_curl)) == NULL) {
150 		RETURN_FALSE;
151 	}
152 
153 	RETVAL_LONG((zend_long)curl_multi_remove_handle(mh->multi, ch->cp));
154 	zend_llist_del_element(&mh->easyh, z_ch, (int (*)(void *, void *))curl_compare_resources);
155 
156 }
157 /* }}} */
158 
_make_timeval_struct(struct timeval * to,double timeout)159 static void _make_timeval_struct(struct timeval *to, double timeout) /* {{{ */
160 {
161 	unsigned long conv;
162 
163 	conv = (unsigned long) (timeout * 1000000.0);
164 	to->tv_sec = conv / 1000000;
165 	to->tv_usec = conv % 1000000;
166 }
167 /* }}} */
168 
169 /* {{{ proto int curl_multi_select(resource mh[, double timeout])
170    Get all the sockets associated with the cURL extension, which can then be "selected" */
PHP_FUNCTION(curl_multi_select)171 PHP_FUNCTION(curl_multi_select)
172 {
173 	zval           *z_mh;
174 	php_curlm      *mh;
175 	fd_set          readfds;
176 	fd_set          writefds;
177 	fd_set          exceptfds;
178 	int             maxfd;
179 	double          timeout = 1.0;
180 	struct timeval  to;
181 
182 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "r|d", &z_mh, &timeout) == FAILURE) {
183 		return;
184 	}
185 
186 	if ((mh = (php_curlm *)zend_fetch_resource(Z_RES_P(z_mh), le_curl_multi_handle_name, le_curl_multi_handle)) == NULL) {
187 		RETURN_FALSE;
188 	}
189 
190 	_make_timeval_struct(&to, timeout);
191 
192 	FD_ZERO(&readfds);
193 	FD_ZERO(&writefds);
194 	FD_ZERO(&exceptfds);
195 
196 	curl_multi_fdset(mh->multi, &readfds, &writefds, &exceptfds, &maxfd);
197 	if (maxfd == -1) {
198 		RETURN_LONG(-1);
199 	}
200 	RETURN_LONG(select(maxfd + 1, &readfds, &writefds, &exceptfds, &to));
201 }
202 /* }}} */
203 
204 /* {{{ proto int curl_multi_exec(resource mh, int &still_running)
205    Run the sub-connections of the current cURL handle */
PHP_FUNCTION(curl_multi_exec)206 PHP_FUNCTION(curl_multi_exec)
207 {
208 	zval      *z_mh;
209 	zval      *z_still_running;
210 	php_curlm *mh;
211 	int        still_running;
212 	int        result;
213 
214 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "rz/", &z_mh, &z_still_running) == FAILURE) {
215 		return;
216 	}
217 
218 	if ((mh = (php_curlm *)zend_fetch_resource(Z_RES_P(z_mh), le_curl_multi_handle_name, le_curl_multi_handle)) == NULL) {
219 		RETURN_FALSE;
220 	}
221 
222 	{
223 		zend_llist_position pos;
224 		php_curl *ch;
225 		zval	*pz_ch;
226 
227 		for (pz_ch = (zval *)zend_llist_get_first_ex(&mh->easyh, &pos); pz_ch;
228 			pz_ch = (zval *)zend_llist_get_next_ex(&mh->easyh, &pos)) {
229 
230 			if ((ch = (php_curl *)zend_fetch_resource(Z_RES_P(pz_ch), le_curl_name, le_curl)) == NULL) {
231 				RETURN_FALSE;
232 			}
233 
234 			_php_curl_verify_handlers(ch, 1);
235 		}
236 	}
237 
238 	convert_to_long(z_still_running);
239 	still_running = Z_LVAL_P(z_still_running);
240 	result = curl_multi_perform(mh->multi, &still_running);
241 	ZVAL_LONG(z_still_running, still_running);
242 
243 	RETURN_LONG(result);
244 }
245 /* }}} */
246 
247 /* {{{ proto string curl_multi_getcontent(resource ch)
248    Return the content of a cURL handle if CURLOPT_RETURNTRANSFER is set */
PHP_FUNCTION(curl_multi_getcontent)249 PHP_FUNCTION(curl_multi_getcontent)
250 {
251 	zval     *z_ch;
252 	php_curl *ch;
253 
254 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &z_ch) == FAILURE) {
255 		return;
256 	}
257 
258 	if ((ch = (php_curl *)zend_fetch_resource(Z_RES_P(z_ch), le_curl_name, le_curl)) == NULL) {
259 		RETURN_FALSE;
260 	}
261 
262 	if (ch->handlers->write->method == PHP_CURL_RETURN) {
263 		if (!ch->handlers->write->buf.s) {
264 			RETURN_EMPTY_STRING();
265 		}
266 		smart_str_0(&ch->handlers->write->buf);
267 		RETURN_STR_COPY(ch->handlers->write->buf.s);
268 	}
269 
270 	RETURN_NULL();
271 }
272 /* }}} */
273 
274 /* {{{ proto array curl_multi_info_read(resource mh [, long msgs_in_queue])
275    Get information about the current transfers */
PHP_FUNCTION(curl_multi_info_read)276 PHP_FUNCTION(curl_multi_info_read)
277 {
278 	zval      *z_mh;
279 	php_curlm *mh;
280 	CURLMsg	  *tmp_msg;
281 	int        queued_msgs;
282 	zval      *zmsgs_in_queue = NULL;
283 
284 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "r|z/", &z_mh, &zmsgs_in_queue) == FAILURE) {
285 		return;
286 	}
287 
288 	if ((mh = (php_curlm *)zend_fetch_resource(Z_RES_P(z_mh), le_curl_multi_handle_name, le_curl_multi_handle)) == NULL) {
289 		RETURN_FALSE;
290 	}
291 
292 	tmp_msg = curl_multi_info_read(mh->multi, &queued_msgs);
293 	if (tmp_msg == NULL) {
294 		RETURN_FALSE;
295 	}
296 	if (zmsgs_in_queue) {
297 		zval_dtor(zmsgs_in_queue);
298 		ZVAL_LONG(zmsgs_in_queue, queued_msgs);
299 	}
300 
301 	array_init(return_value);
302 	add_assoc_long(return_value, "msg", tmp_msg->msg);
303 	add_assoc_long(return_value, "result", tmp_msg->data.result);
304 
305 	/* find the original easy curl handle */
306 	{
307 		zend_llist_position pos;
308 		php_curl *ch;
309 		zval	*pz_ch;
310 
311 		/* search the list of easy handles hanging off the multi-handle */
312 		for(pz_ch = (zval *)zend_llist_get_first_ex(&mh->easyh, &pos); pz_ch;
313 			pz_ch = (zval *)zend_llist_get_next_ex(&mh->easyh, &pos)) {
314 
315 			if ((ch = (php_curl *)zend_fetch_resource(Z_RES_P(pz_ch), le_curl_name, le_curl)) == NULL) {
316 				RETURN_FALSE;
317 			}
318 			if (ch->cp == tmp_msg->easy_handle) {
319 
320 				/* we are adding a reference to the underlying php_curl
321 				   resource, so we need to add one to the resource's refcount
322 				   in order to ensure it doesn't get destroyed when the
323 				   underlying curl easy handle goes out of scope.
324 				   Normally you would call zval_copy_ctor( pz_ch ), or
325 				   SEPARATE_ZVAL, but those create new zvals, which is already
326 				   being done in add_assoc_resource */
327 				Z_ADDREF_P(pz_ch);
328 
329 				/* add_assoc_resource automatically creates a new zval to
330 				   wrap the "resource" represented by the current pz_ch */
331 
332 				add_assoc_zval(return_value, "handle", pz_ch);
333 
334 				break;
335 			}
336 		}
337 	}
338 }
339 /* }}} */
340 
341 /* {{{ proto void curl_multi_close(resource mh)
342    Close a set of cURL handles */
PHP_FUNCTION(curl_multi_close)343 PHP_FUNCTION(curl_multi_close)
344 {
345 	zval      *z_mh;
346 	php_curlm *mh;
347 
348 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &z_mh) == FAILURE) {
349 		return;
350 	}
351 
352 	if ((mh = (php_curlm *)zend_fetch_resource(Z_RES_P(z_mh), le_curl_multi_handle_name, le_curl_multi_handle)) == NULL) {
353 		RETURN_FALSE;
354 	}
355 
356 	zend_list_close(Z_RES_P(z_mh));
357 }
358 /* }}} */
359 
_php_curl_multi_close(zend_resource * rsrc)360 void _php_curl_multi_close(zend_resource *rsrc) /* {{{ */
361 {
362 	php_curlm *mh = (php_curlm *)rsrc->ptr;
363 	if (mh) {
364 		zend_llist_position pos;
365 		php_curl *ch;
366 		zval	*pz_ch;
367 
368 		for (pz_ch = (zval *)zend_llist_get_first_ex(&mh->easyh, &pos); pz_ch;
369 			pz_ch = (zval *)zend_llist_get_next_ex(&mh->easyh, &pos)) {
370 			/* ptr is NULL means it already be freed */
371 			if (Z_RES_P(pz_ch)->ptr) {
372 				if ((ch = (php_curl *) zend_fetch_resource(Z_RES_P(pz_ch), le_curl_name, le_curl))) {
373 					_php_curl_verify_handlers(ch, 0);
374 				}
375 			}
376 		}
377 
378 		curl_multi_cleanup(mh->multi);
379 		zend_llist_clean(&mh->easyh);
380 		efree(mh);
381 		rsrc->ptr = NULL;
382 	}
383 }
384 /* }}} */
385 
386 #if LIBCURL_VERSION_NUM >= 0x070c00 /* Available since 7.12.0 */
387 /* {{{ proto bool curl_multi_strerror(int code)
388          return string describing error code */
PHP_FUNCTION(curl_multi_strerror)389 PHP_FUNCTION(curl_multi_strerror)
390 {
391 	zend_long code;
392 	const char *str;
393 
394 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &code) == FAILURE) {
395 		return;
396 	}
397 
398 	str = curl_multi_strerror(code);
399 	if (str) {
400 		RETURN_STRING(str);
401 	} else {
402 		RETURN_NULL();
403 	}
404 }
405 /* }}} */
406 #endif
407 
408 #if LIBCURL_VERSION_NUM >= 0x070f04 /* 7.15.4 */
_php_curl_multi_setopt(php_curlm * mh,zend_long option,zval * zvalue,zval * return_value)409 static int _php_curl_multi_setopt(php_curlm *mh, zend_long option, zval *zvalue, zval *return_value) /* {{{ */
410 {
411 	CURLMcode error = CURLM_OK;
412 
413 	switch (option) {
414 #if LIBCURL_VERSION_NUM >= 0x071000 /* 7.16.0 */
415 		case CURLMOPT_PIPELINING:
416 #endif
417 #if LIBCURL_VERSION_NUM >= 0x071003 /* 7.16.3 */
418 		case CURLMOPT_MAXCONNECTS:
419 #endif
420 #if LIBCURL_VERSION_NUM >= 0x071e00 /* 7.30.0 */
421 		case CURLMOPT_CHUNK_LENGTH_PENALTY_SIZE:
422 		case CURLMOPT_CONTENT_LENGTH_PENALTY_SIZE:
423 		case CURLMOPT_MAX_HOST_CONNECTIONS:
424 		case CURLMOPT_MAX_PIPELINE_LENGTH:
425 		case CURLMOPT_MAX_TOTAL_CONNECTIONS:
426 #endif
427 			error = curl_multi_setopt(mh->multi, option, zval_get_long(zvalue));
428 			break;
429 
430 		default:
431 			php_error_docref(NULL, E_WARNING, "Invalid curl multi configuration option");
432 			error = CURLM_UNKNOWN_OPTION;
433 			break;
434 	}
435 
436 	if (error != CURLM_OK) {
437 		return 1;
438 	} else {
439 		return 0;
440 	}
441 }
442 /* }}} */
443 
444 /* {{{ proto int curl_multi_setopt(resource mh, int option, mixed value)
445        Set an option for the curl multi handle */
PHP_FUNCTION(curl_multi_setopt)446 PHP_FUNCTION(curl_multi_setopt)
447 {
448 	zval       *z_mh, *zvalue;
449 	zend_long        options;
450 	php_curlm *mh;
451 
452 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlz", &z_mh, &options, &zvalue) == FAILURE) {
453 		return;
454 	}
455 
456 	if ((mh = (php_curlm *)zend_fetch_resource(Z_RES_P(z_mh), le_curl_multi_handle_name, le_curl_multi_handle)) == NULL) {
457 		RETURN_FALSE;
458 	}
459 
460 	if (!_php_curl_multi_setopt(mh, options, zvalue, return_value)) {
461 		RETURN_TRUE;
462 	} else {
463 		RETURN_FALSE;
464 	}
465 }
466 /* }}} */
467 #endif
468 
469 #endif
470 
471 /*
472  * Local variables:
473  * tab-width: 4
474  * c-basic-offset: 4
475  * End:
476  * vim600: noet sw=4 ts=4 fdm=marker
477  * vim<600: noet sw=4 ts=4
478  */
479