xref: /PHP-5.5/ext/curl/multi.c (revision 73c1be26)
1 /*
2    +----------------------------------------------------------------------+
3    | PHP Version 5                                                        |
4    +----------------------------------------------------------------------+
5    | Copyright (c) 1997-2015 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 	ZEND_REGISTER_RESOURCE(return_value, 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() TSRMLS_CC, "rr", &z_mh, &z_ch) == FAILURE) {
82 		return;
83 	}
84 
85 	ZEND_FETCH_RESOURCE(mh, php_curlm *, &z_mh, -1, le_curl_multi_handle_name, le_curl_multi_handle);
86 	ZEND_FETCH_RESOURCE(ch, php_curl *, &z_ch, -1, le_curl_name, le_curl);
87 
88 	_php_curl_cleanup_handle(ch);
89 
90 	/* we want to create a copy of this zval that we store in the multihandle structure element "easyh" */
91 	tmp_val = *z_ch;
92 	zval_copy_ctor(&tmp_val);
93 
94 	zend_llist_add_element(&mh->easyh, &tmp_val);
95 
96 	RETURN_LONG((long) curl_multi_add_handle(mh->multi, ch->cp));
97 }
98 /* }}} */
99 
_php_curl_multi_cleanup_list(void * data)100 void _php_curl_multi_cleanup_list(void *data) /* {{{ */
101 {
102 	zval *z_ch = (zval *)data;
103 	php_curl *ch;
104 	TSRMLS_FETCH();
105 
106 	if (!z_ch) {
107 		return;
108 	}
109 
110 	ch = (php_curl *) zend_fetch_resource(&z_ch TSRMLS_CC, -1, le_curl_name, NULL, 1, le_curl);
111 	if (!ch) {
112 		return;
113 	}
114 
115 	zend_list_delete(Z_LVAL_P(z_ch));
116 }
117 /* }}} */
118 
119 /* Used internally as comparison routine passed to zend_list_del_element */
curl_compare_resources(zval * z1,zval ** z2)120 static int curl_compare_resources( zval *z1, zval **z2 ) /* {{{ */
121 {
122 	return (Z_TYPE_P( z1 ) == Z_TYPE_PP( z2 ) &&
123 			Z_TYPE_P( z1 ) == IS_RESOURCE     &&
124 			Z_LVAL_P( z1 ) == Z_LVAL_PP( z2 ) );
125 }
126 /* }}} */
127 
128 /* {{{ proto int curl_multi_remove_handle(resource mh, resource ch)
129    Remove a multi handle from a set of cURL handles */
PHP_FUNCTION(curl_multi_remove_handle)130 PHP_FUNCTION(curl_multi_remove_handle)
131 {
132 	zval      *z_mh;
133 	zval      *z_ch;
134 	php_curlm *mh;
135 	php_curl  *ch;
136 
137 	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rr", &z_mh, &z_ch) == FAILURE) {
138 		return;
139 	}
140 
141 	ZEND_FETCH_RESOURCE(mh, php_curlm *, &z_mh, -1, le_curl_multi_handle_name, le_curl_multi_handle);
142 	ZEND_FETCH_RESOURCE(ch, php_curl *, &z_ch, -1, le_curl_name, le_curl);
143 
144 
145 
146 	RETVAL_LONG((long) curl_multi_remove_handle(mh->multi, ch->cp));
147 	zend_llist_del_element( &mh->easyh, &z_ch,
148 							(int (*)(void *, void *)) curl_compare_resources );
149 
150 }
151 /* }}} */
152 
_make_timeval_struct(struct timeval * to,double timeout)153 static void _make_timeval_struct(struct timeval *to, double timeout) /* {{{ */
154 {
155 	unsigned long conv;
156 
157 	conv = (unsigned long) (timeout * 1000000.0);
158 	to->tv_sec = conv / 1000000;
159 	to->tv_usec = conv % 1000000;
160 }
161 /* }}} */
162 
163 /* {{{ proto int curl_multi_select(resource mh[, double timeout])
164    Get all the sockets associated with the cURL extension, which can then be "selected" */
PHP_FUNCTION(curl_multi_select)165 PHP_FUNCTION(curl_multi_select)
166 {
167 	zval           *z_mh;
168 	php_curlm      *mh;
169 	fd_set          readfds;
170 	fd_set          writefds;
171 	fd_set          exceptfds;
172 	int             maxfd;
173 	double          timeout = 1.0;
174 	struct timeval  to;
175 
176 	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r|d", &z_mh, &timeout) == FAILURE) {
177 		return;
178 	}
179 
180 	ZEND_FETCH_RESOURCE(mh, php_curlm *, &z_mh, -1, le_curl_multi_handle_name, le_curl_multi_handle);
181 
182 	_make_timeval_struct(&to, timeout);
183 
184 	FD_ZERO(&readfds);
185 	FD_ZERO(&writefds);
186 	FD_ZERO(&exceptfds);
187 
188 	curl_multi_fdset(mh->multi, &readfds, &writefds, &exceptfds, &maxfd);
189 	if (maxfd == -1) {
190 		RETURN_LONG(-1);
191 	}
192 	RETURN_LONG(select(maxfd + 1, &readfds, &writefds, &exceptfds, &to));
193 }
194 /* }}} */
195 
196 /* {{{ proto int curl_multi_exec(resource mh, int &still_running)
197    Run the sub-connections of the current cURL handle */
PHP_FUNCTION(curl_multi_exec)198 PHP_FUNCTION(curl_multi_exec)
199 {
200 	zval      *z_mh;
201 	zval      *z_still_running;
202 	php_curlm *mh;
203 	int        still_running;
204 	int        result;
205 
206 	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rz", &z_mh, &z_still_running) == FAILURE) {
207 		return;
208 	}
209 
210 	ZEND_FETCH_RESOURCE(mh, php_curlm *, &z_mh, -1, le_curl_multi_handle_name, le_curl_multi_handle);
211 
212 	{
213 		zend_llist_position pos;
214 		php_curl *ch;
215 		zval	*pz_ch;
216 
217 		for(pz_ch = (zval *)zend_llist_get_first_ex(&mh->easyh, &pos); pz_ch;
218 			pz_ch = (zval *)zend_llist_get_next_ex(&mh->easyh, &pos)) {
219 
220 			ZEND_FETCH_RESOURCE(ch, php_curl *, &pz_ch, -1, le_curl_name, le_curl);
221 			_php_curl_verify_handlers(ch, 1 TSRMLS_CC);
222 		}
223 	}
224 
225 	convert_to_long_ex(&z_still_running);
226 	still_running = Z_LVAL_P(z_still_running);
227 	result = curl_multi_perform(mh->multi, &still_running);
228 	ZVAL_LONG(z_still_running, still_running);
229 
230 	RETURN_LONG(result);
231 }
232 /* }}} */
233 
234 /* {{{ proto string curl_multi_getcontent(resource ch)
235    Return the content of a cURL handle if CURLOPT_RETURNTRANSFER is set */
PHP_FUNCTION(curl_multi_getcontent)236 PHP_FUNCTION(curl_multi_getcontent)
237 {
238 	zval     *z_ch;
239 	php_curl *ch;
240 
241 	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &z_ch) == FAILURE) {
242 		return;
243 	}
244 
245 	ZEND_FETCH_RESOURCE(ch, php_curl *, &z_ch, -1, le_curl_name, le_curl);
246 
247 	if (ch->handlers->write->method == PHP_CURL_RETURN) {
248 		if (ch->handlers->write->buf.len == 0) {
249 			RETURN_EMPTY_STRING();
250 		}
251 		smart_str_0(&ch->handlers->write->buf);
252 		RETURN_STRINGL(ch->handlers->write->buf.c, ch->handlers->write->buf.len, 1);
253 	}
254 
255 	RETURN_NULL();
256 }
257 /* }}} */
258 
259 /* {{{ proto array curl_multi_info_read(resource mh [, long msgs_in_queue])
260    Get information about the current transfers */
PHP_FUNCTION(curl_multi_info_read)261 PHP_FUNCTION(curl_multi_info_read)
262 {
263 	zval      *z_mh;
264 	php_curlm *mh;
265 	CURLMsg	  *tmp_msg;
266 	int        queued_msgs;
267 	zval      *zmsgs_in_queue = NULL;
268 
269 	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r|z", &z_mh, &zmsgs_in_queue) == FAILURE) {
270 		return;
271 	}
272 
273 	ZEND_FETCH_RESOURCE(mh, php_curlm *, &z_mh, -1, le_curl_multi_handle_name, le_curl_multi_handle);
274 
275 	tmp_msg = curl_multi_info_read(mh->multi, &queued_msgs);
276 	if (tmp_msg == NULL) {
277 		RETURN_FALSE;
278 	}
279 	if (zmsgs_in_queue) {
280 		zval_dtor(zmsgs_in_queue);
281 		ZVAL_LONG(zmsgs_in_queue, queued_msgs);
282 	}
283 
284 	array_init(return_value);
285 	add_assoc_long(return_value, "msg", tmp_msg->msg);
286 	add_assoc_long(return_value, "result", tmp_msg->data.result);
287 
288 	/* find the original easy curl handle */
289 	{
290 		zend_llist_position pos;
291 		php_curl *ch;
292 		zval	*pz_ch;
293 
294 		/* search the list of easy handles hanging off the multi-handle */
295 		for(pz_ch = (zval *)zend_llist_get_first_ex(&mh->easyh, &pos); pz_ch;
296 			pz_ch = (zval *)zend_llist_get_next_ex(&mh->easyh, &pos)) {
297 			ZEND_FETCH_RESOURCE(ch, php_curl *, &pz_ch, -1, le_curl_name, le_curl);
298 			if (ch->cp == tmp_msg->easy_handle) {
299 
300 				/* we are adding a reference to the underlying php_curl
301 				   resource, so we need to add one to the resource's refcount
302 				   in order to ensure it doesn't get destroyed when the
303 				   underlying curl easy handle goes out of scope.
304 				   Normally you would call zval_copy_ctor( pz_ch ), or
305 				   SEPARATE_ZVAL, but those create new zvals, which is already
306 				   being done in add_assoc_resource */
307 
308 				zend_list_addref( Z_RESVAL_P( pz_ch ) );
309 
310 				/* add_assoc_resource automatically creates a new zval to
311 				   wrap the "resource" represented by the current pz_ch */
312 
313 				add_assoc_resource(return_value, "handle", Z_RESVAL_P(pz_ch));
314 
315 				break;
316 			}
317 		}
318 	}
319 }
320 /* }}} */
321 
322 /* {{{ proto void curl_multi_close(resource mh)
323    Close a set of cURL handles */
PHP_FUNCTION(curl_multi_close)324 PHP_FUNCTION(curl_multi_close)
325 {
326 	zval      *z_mh;
327 	php_curlm *mh;
328 
329 	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &z_mh) == FAILURE) {
330 		return;
331 	}
332 
333 	ZEND_FETCH_RESOURCE(mh, php_curlm *, &z_mh, -1, le_curl_multi_handle_name, le_curl_multi_handle);
334 
335 	zend_list_delete(Z_LVAL_P(z_mh));
336 }
337 /* }}} */
338 
_php_curl_multi_close(zend_rsrc_list_entry * rsrc TSRMLS_DC)339 void _php_curl_multi_close(zend_rsrc_list_entry *rsrc TSRMLS_DC) /* {{{ */
340 {
341 	php_curlm *mh = (php_curlm *) rsrc->ptr;
342 	if (mh) {
343 		zend_llist_position pos;
344 		php_curl *ch;
345 		zval	*pz_ch;
346 
347 		for(pz_ch = (zval *)zend_llist_get_first_ex(&mh->easyh, &pos); pz_ch;
348 			pz_ch = (zval *)zend_llist_get_next_ex(&mh->easyh, &pos)) {
349 
350 			ch = (php_curl *) zend_fetch_resource(&pz_ch TSRMLS_CC, -1, le_curl_name, NULL, 1, le_curl);
351 			_php_curl_verify_handlers(ch, 0 TSRMLS_CC);
352 		}
353 
354 		curl_multi_cleanup(mh->multi);
355 		zend_llist_clean(&mh->easyh);
356 		efree(mh);
357 		rsrc->ptr = NULL;
358 	}
359 }
360 /* }}} */
361 
362 #if LIBCURL_VERSION_NUM >= 0x070c00 /* Available since 7.12.0 */
363 /* {{{ proto bool curl_multi_strerror(int code)
364          return string describing error code */
PHP_FUNCTION(curl_multi_strerror)365 PHP_FUNCTION(curl_multi_strerror)
366 {
367 	long code;
368 	const char *str;
369 
370 	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &code) == FAILURE) {
371 		return;
372 	}
373 
374 	str = curl_multi_strerror(code);
375 	if (str) {
376 		RETURN_STRING(str, 1);
377 	} else {
378 		RETURN_NULL();
379 	}
380 }
381 /* }}} */
382 #endif
383 
384 #if LIBCURL_VERSION_NUM >= 0x070f04 /* 7.15.4 */
_php_curl_multi_setopt(php_curlm * mh,long option,zval ** zvalue,zval * return_value TSRMLS_DC)385 static int _php_curl_multi_setopt(php_curlm *mh, long option, zval **zvalue, zval *return_value TSRMLS_DC) /* {{{ */
386 {
387 	CURLMcode error = CURLM_OK;
388 
389 	switch (option) {
390 #if LIBCURL_VERSION_NUM >= 0x071000 /* 7.16.0 */
391 		case CURLMOPT_PIPELINING:
392 #endif
393 #if LIBCURL_VERSION_NUM >= 0x071003 /* 7.16.3 */
394 		case CURLMOPT_MAXCONNECTS:
395 #endif
396 			convert_to_long_ex(zvalue);
397 			error = curl_multi_setopt(mh->multi, option, Z_LVAL_PP(zvalue));
398 			break;
399 
400 		default:
401 			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid curl multi configuration option");
402 			error = CURLM_UNKNOWN_OPTION;
403 			break;
404 	}
405 
406 	if (error != CURLM_OK) {
407 		return 1;
408 	} else {
409 		return 0;
410 	}
411 }
412 /* }}} */
413 
414 /* {{{ proto int curl_multi_setopt(resource mh, int option, mixed value)
415        Set an option for the curl multi handle */
PHP_FUNCTION(curl_multi_setopt)416 PHP_FUNCTION(curl_multi_setopt)
417 {
418 	zval       *z_mh, **zvalue;
419 	long        options;
420 	php_curlm *mh;
421 
422 	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rlZ", &z_mh, &options, &zvalue) == FAILURE) {
423 		return;
424 	}
425 
426 	ZEND_FETCH_RESOURCE(mh, php_curlm *, &z_mh, -1, le_curl_multi_handle_name, le_curl_multi_handle);
427 
428 	if (!_php_curl_multi_setopt(mh, options, zvalue, return_value TSRMLS_CC)) {
429 		RETURN_TRUE;
430 	} else {
431 		RETURN_FALSE;
432 	}
433 }
434 /* }}} */
435 #endif
436 
437 #endif
438 
439 /*
440  * Local variables:
441  * tab-width: 4
442  * c-basic-offset: 4
443  * End:
444  * vim600: noet sw=4 ts=4 fdm=marker
445  * vim<600: noet sw=4 ts=4
446  */
447