multi.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  1. /*
  2. +----------------------------------------------------------------------+
  3. | Copyright (c) The PHP Group |
  4. +----------------------------------------------------------------------+
  5. | This source file is subject to version 3.01 of the PHP license, |
  6. | that is bundled with this package in the file LICENSE, and is |
  7. | available through the world-wide-web at the following url: |
  8. | https://www.php.net/license/3_01.txt |
  9. | If you did not receive a copy of the PHP license and are unable to |
  10. | obtain it through the world-wide-web, please send a note to |
  11. | license@php.net so we can mail you a copy immediately. |
  12. +----------------------------------------------------------------------+
  13. | Author: Sterling Hughes <sterling@php.net> |
  14. +----------------------------------------------------------------------+
  15. */
  16. #define ZEND_INCLUDE_FULL_WINDOWS_HEADERS
  17. #ifdef HAVE_CONFIG_H
  18. #include "config.h"
  19. #endif
  20. #include "php.h"
  21. #include "Zend/zend_smart_str.h"
  22. #include "curl_private.h"
  23. #include <curl/curl.h>
  24. #include <curl/multi.h>
  25. #ifdef HAVE_SYS_SELECT_H
  26. #include <sys/select.h>
  27. #endif
  28. #ifdef HAVE_SYS_TIME_H
  29. #include <sys/time.h>
  30. #endif
  31. #ifdef HAVE_SYS_TYPES_H
  32. #include <sys/types.h>
  33. #endif
  34. #ifdef HAVE_UNISTD_H
  35. #include <unistd.h>
  36. #endif
  37. #define SAVE_CURLM_ERROR(__handle, __err) (__handle)->err.no = (int) __err;
  38. /* CurlMultiHandle class */
  39. zend_class_entry *curl_multi_ce;
  40. static inline php_curlm *curl_multi_from_obj(zend_object *obj) {
  41. return (php_curlm *)((char *)(obj) - XtOffsetOf(php_curlm, std));
  42. }
  43. #define Z_CURL_MULTI_P(zv) curl_multi_from_obj(Z_OBJ_P(zv))
  44. /* {{{ Returns a new cURL multi handle */
  45. PHP_FUNCTION(curl_multi_init)
  46. {
  47. php_curlm *mh;
  48. ZEND_PARSE_PARAMETERS_NONE();
  49. object_init_ex(return_value, curl_multi_ce);
  50. mh = Z_CURL_MULTI_P(return_value);
  51. mh->multi = curl_multi_init();
  52. zend_llist_init(&mh->easyh, sizeof(zval), _php_curl_multi_cleanup_list, 0);
  53. }
  54. /* }}} */
  55. /* {{{ Add a normal cURL handle to a cURL multi handle */
  56. PHP_FUNCTION(curl_multi_add_handle)
  57. {
  58. zval *z_mh;
  59. zval *z_ch;
  60. php_curlm *mh;
  61. php_curl *ch;
  62. CURLMcode error = CURLM_OK;
  63. ZEND_PARSE_PARAMETERS_START(2,2)
  64. Z_PARAM_OBJECT_OF_CLASS(z_mh, curl_multi_ce)
  65. Z_PARAM_OBJECT_OF_CLASS(z_ch, curl_ce)
  66. ZEND_PARSE_PARAMETERS_END();
  67. mh = Z_CURL_MULTI_P(z_mh);
  68. ch = Z_CURL_P(z_ch);
  69. _php_curl_verify_handlers(ch, 1);
  70. _php_curl_cleanup_handle(ch);
  71. Z_ADDREF_P(z_ch);
  72. zend_llist_add_element(&mh->easyh, z_ch);
  73. error = curl_multi_add_handle(mh->multi, ch->cp);
  74. SAVE_CURLM_ERROR(mh, error);
  75. RETURN_LONG((zend_long) error);
  76. }
  77. /* }}} */
  78. void _php_curl_multi_cleanup_list(void *data) /* {{{ */
  79. {
  80. zval *z_ch = (zval *)data;
  81. zval_ptr_dtor(z_ch);
  82. }
  83. /* }}} */
  84. /* Used internally as comparison routine passed to zend_list_del_element */
  85. static int curl_compare_objects( zval *z1, zval *z2 ) /* {{{ */
  86. {
  87. return (Z_TYPE_P(z1) == Z_TYPE_P(z2) &&
  88. Z_TYPE_P(z1) == IS_OBJECT &&
  89. Z_OBJ_P(z1) == Z_OBJ_P(z2));
  90. }
  91. /* }}} */
  92. /* Used to find the php_curl resource for a given curl easy handle */
  93. static zval *_php_curl_multi_find_easy_handle(php_curlm *mh, CURL *easy) /* {{{ */
  94. {
  95. php_curl *tmp_ch;
  96. zend_llist_position pos;
  97. zval *pz_ch_temp;
  98. for(pz_ch_temp = (zval *)zend_llist_get_first_ex(&mh->easyh, &pos); pz_ch_temp;
  99. pz_ch_temp = (zval *)zend_llist_get_next_ex(&mh->easyh, &pos)) {
  100. tmp_ch = Z_CURL_P(pz_ch_temp);
  101. if (tmp_ch->cp == easy) {
  102. return pz_ch_temp;
  103. }
  104. }
  105. return NULL;
  106. }
  107. /* }}} */
  108. /* {{{ Remove a multi handle from a set of cURL handles */
  109. PHP_FUNCTION(curl_multi_remove_handle)
  110. {
  111. zval *z_mh;
  112. zval *z_ch;
  113. php_curlm *mh;
  114. php_curl *ch;
  115. CURLMcode error = CURLM_OK;
  116. ZEND_PARSE_PARAMETERS_START(2,2)
  117. Z_PARAM_OBJECT_OF_CLASS(z_mh, curl_multi_ce)
  118. Z_PARAM_OBJECT_OF_CLASS(z_ch, curl_ce)
  119. ZEND_PARSE_PARAMETERS_END();
  120. mh = Z_CURL_MULTI_P(z_mh);
  121. ch = Z_CURL_P(z_ch);
  122. error = curl_multi_remove_handle(mh->multi, ch->cp);
  123. SAVE_CURLM_ERROR(mh, error);
  124. RETVAL_LONG((zend_long) error);
  125. zend_llist_del_element(&mh->easyh, z_ch, (int (*)(void *, void *))curl_compare_objects);
  126. }
  127. /* }}} */
  128. /* {{{ Get all the sockets associated with the cURL extension, which can then be "selected" */
  129. PHP_FUNCTION(curl_multi_select)
  130. {
  131. zval *z_mh;
  132. php_curlm *mh;
  133. double timeout = 1.0;
  134. int numfds = 0;
  135. CURLMcode error = CURLM_OK;
  136. ZEND_PARSE_PARAMETERS_START(1,2)
  137. Z_PARAM_OBJECT_OF_CLASS(z_mh, curl_multi_ce)
  138. Z_PARAM_OPTIONAL
  139. Z_PARAM_DOUBLE(timeout)
  140. ZEND_PARSE_PARAMETERS_END();
  141. mh = Z_CURL_MULTI_P(z_mh);
  142. error = curl_multi_wait(mh->multi, NULL, 0, (unsigned long) (timeout * 1000.0), &numfds);
  143. if (CURLM_OK != error) {
  144. SAVE_CURLM_ERROR(mh, error);
  145. RETURN_LONG(-1);
  146. }
  147. RETURN_LONG(numfds);
  148. }
  149. /* }}} */
  150. /* {{{ Run the sub-connections of the current cURL handle */
  151. PHP_FUNCTION(curl_multi_exec)
  152. {
  153. zval *z_mh;
  154. zval *z_still_running;
  155. php_curlm *mh;
  156. int still_running;
  157. CURLMcode error = CURLM_OK;
  158. ZEND_PARSE_PARAMETERS_START(2, 2)
  159. Z_PARAM_OBJECT_OF_CLASS(z_mh, curl_multi_ce)
  160. Z_PARAM_ZVAL(z_still_running)
  161. ZEND_PARSE_PARAMETERS_END();
  162. mh = Z_CURL_MULTI_P(z_mh);
  163. {
  164. zend_llist_position pos;
  165. php_curl *ch;
  166. zval *pz_ch;
  167. for (pz_ch = (zval *)zend_llist_get_first_ex(&mh->easyh, &pos); pz_ch;
  168. pz_ch = (zval *)zend_llist_get_next_ex(&mh->easyh, &pos)) {
  169. ch = Z_CURL_P(pz_ch);
  170. _php_curl_verify_handlers(ch, 1);
  171. }
  172. }
  173. still_running = zval_get_long(z_still_running);
  174. error = curl_multi_perform(mh->multi, &still_running);
  175. ZEND_TRY_ASSIGN_REF_LONG(z_still_running, still_running);
  176. SAVE_CURLM_ERROR(mh, error);
  177. RETURN_LONG((zend_long) error);
  178. }
  179. /* }}} */
  180. /* {{{ Return the content of a cURL handle if CURLOPT_RETURNTRANSFER is set */
  181. PHP_FUNCTION(curl_multi_getcontent)
  182. {
  183. zval *z_ch;
  184. php_curl *ch;
  185. ZEND_PARSE_PARAMETERS_START(1, 1)
  186. Z_PARAM_OBJECT_OF_CLASS(z_ch, curl_ce)
  187. ZEND_PARSE_PARAMETERS_END();
  188. ch = Z_CURL_P(z_ch);
  189. if (ch->handlers.write->method == PHP_CURL_RETURN) {
  190. if (!ch->handlers.write->buf.s) {
  191. RETURN_EMPTY_STRING();
  192. }
  193. smart_str_0(&ch->handlers.write->buf);
  194. RETURN_STR_COPY(ch->handlers.write->buf.s);
  195. }
  196. RETURN_NULL();
  197. }
  198. /* }}} */
  199. /* {{{ Get information about the current transfers */
  200. PHP_FUNCTION(curl_multi_info_read)
  201. {
  202. zval *z_mh;
  203. php_curlm *mh;
  204. CURLMsg *tmp_msg;
  205. int queued_msgs;
  206. zval *zmsgs_in_queue = NULL;
  207. php_curl *ch;
  208. ZEND_PARSE_PARAMETERS_START(1, 2)
  209. Z_PARAM_OBJECT_OF_CLASS(z_mh, curl_multi_ce)
  210. Z_PARAM_OPTIONAL
  211. Z_PARAM_ZVAL(zmsgs_in_queue)
  212. ZEND_PARSE_PARAMETERS_END();
  213. mh = Z_CURL_MULTI_P(z_mh);
  214. tmp_msg = curl_multi_info_read(mh->multi, &queued_msgs);
  215. if (tmp_msg == NULL) {
  216. RETURN_FALSE;
  217. }
  218. if (zmsgs_in_queue) {
  219. ZEND_TRY_ASSIGN_REF_LONG(zmsgs_in_queue, queued_msgs);
  220. }
  221. array_init(return_value);
  222. add_assoc_long(return_value, "msg", tmp_msg->msg);
  223. add_assoc_long(return_value, "result", tmp_msg->data.result);
  224. /* find the original easy curl handle */
  225. {
  226. zval *pz_ch = _php_curl_multi_find_easy_handle(mh, tmp_msg->easy_handle);
  227. if (pz_ch != NULL) {
  228. /* we must save result to be able to read error message */
  229. ch = Z_CURL_P(pz_ch);
  230. SAVE_CURL_ERROR(ch, tmp_msg->data.result);
  231. Z_ADDREF_P(pz_ch);
  232. add_assoc_zval(return_value, "handle", pz_ch);
  233. }
  234. }
  235. }
  236. /* }}} */
  237. /* {{{ Close a set of cURL handles */
  238. PHP_FUNCTION(curl_multi_close)
  239. {
  240. php_curlm *mh;
  241. zval *z_mh;
  242. zend_llist_position pos;
  243. zval *pz_ch;
  244. ZEND_PARSE_PARAMETERS_START(1,1)
  245. Z_PARAM_OBJECT_OF_CLASS(z_mh, curl_multi_ce)
  246. ZEND_PARSE_PARAMETERS_END();
  247. mh = Z_CURL_MULTI_P(z_mh);
  248. for (pz_ch = (zval *)zend_llist_get_first_ex(&mh->easyh, &pos); pz_ch;
  249. pz_ch = (zval *)zend_llist_get_next_ex(&mh->easyh, &pos)) {
  250. php_curl *ch = Z_CURL_P(pz_ch);
  251. _php_curl_verify_handlers(ch, 1);
  252. curl_multi_remove_handle(mh->multi, ch->cp);
  253. }
  254. zend_llist_clean(&mh->easyh);
  255. }
  256. /* }}} */
  257. /* {{{ Return an integer containing the last multi curl error number */
  258. PHP_FUNCTION(curl_multi_errno)
  259. {
  260. zval *z_mh;
  261. php_curlm *mh;
  262. ZEND_PARSE_PARAMETERS_START(1,1)
  263. Z_PARAM_OBJECT_OF_CLASS(z_mh, curl_multi_ce)
  264. ZEND_PARSE_PARAMETERS_END();
  265. mh = Z_CURL_MULTI_P(z_mh);
  266. RETURN_LONG(mh->err.no);
  267. }
  268. /* }}} */
  269. /* {{{ return string describing error code */
  270. PHP_FUNCTION(curl_multi_strerror)
  271. {
  272. zend_long code;
  273. const char *str;
  274. ZEND_PARSE_PARAMETERS_START(1,1)
  275. Z_PARAM_LONG(code)
  276. ZEND_PARSE_PARAMETERS_END();
  277. str = curl_multi_strerror(code);
  278. if (str) {
  279. RETURN_STRING(str);
  280. } else {
  281. RETURN_NULL();
  282. }
  283. }
  284. /* }}} */
  285. #if LIBCURL_VERSION_NUM >= 0x072C00 /* Available since 7.44.0 */
  286. static int _php_server_push_callback(CURL *parent_ch, CURL *easy, size_t num_headers, struct curl_pushheaders *push_headers, void *userp) /* {{{ */
  287. {
  288. php_curl *ch;
  289. php_curl *parent;
  290. php_curlm *mh = (php_curlm *)userp;
  291. size_t rval = CURL_PUSH_DENY;
  292. php_curl_callback *t = mh->handlers.server_push;
  293. zval *pz_parent_ch = NULL;
  294. zval pz_ch;
  295. zval headers;
  296. zval retval;
  297. char *header;
  298. int error;
  299. zend_fcall_info fci = empty_fcall_info;
  300. pz_parent_ch = _php_curl_multi_find_easy_handle(mh, parent_ch);
  301. if (pz_parent_ch == NULL) {
  302. return rval;
  303. }
  304. parent = Z_CURL_P(pz_parent_ch);
  305. ch = init_curl_handle_into_zval(&pz_ch);
  306. ch->cp = easy;
  307. _php_setup_easy_copy_handlers(ch, parent);
  308. size_t i;
  309. array_init(&headers);
  310. for(i=0; i<num_headers; i++) {
  311. header = curl_pushheader_bynum(push_headers, i);
  312. add_next_index_string(&headers, header);
  313. }
  314. zend_fcall_info_init(&t->func_name, 0, &fci, &t->fci_cache, NULL, NULL);
  315. zend_fcall_info_argn(
  316. &fci, 3,
  317. pz_parent_ch,
  318. &pz_ch,
  319. &headers
  320. );
  321. fci.retval = &retval;
  322. error = zend_call_function(&fci, &t->fci_cache);
  323. zend_fcall_info_args_clear(&fci, 1);
  324. zval_ptr_dtor_nogc(&headers);
  325. if (error == FAILURE) {
  326. php_error_docref(NULL, E_WARNING, "Cannot call the CURLMOPT_PUSHFUNCTION");
  327. } else if (!Z_ISUNDEF(retval)) {
  328. if (CURL_PUSH_DENY != zval_get_long(&retval)) {
  329. rval = CURL_PUSH_OK;
  330. zend_llist_add_element(&mh->easyh, &pz_ch);
  331. } else {
  332. /* libcurl will free this easy handle, avoid double free */
  333. ch->cp = NULL;
  334. }
  335. }
  336. return rval;
  337. }
  338. /* }}} */
  339. #endif
  340. static int _php_curl_multi_setopt(php_curlm *mh, zend_long option, zval *zvalue, zval *return_value) /* {{{ */
  341. {
  342. CURLMcode error = CURLM_OK;
  343. switch (option) {
  344. case CURLMOPT_PIPELINING:
  345. case CURLMOPT_MAXCONNECTS:
  346. #if LIBCURL_VERSION_NUM >= 0x071e00 /* 7.30.0 */
  347. case CURLMOPT_CHUNK_LENGTH_PENALTY_SIZE:
  348. case CURLMOPT_CONTENT_LENGTH_PENALTY_SIZE:
  349. case CURLMOPT_MAX_HOST_CONNECTIONS:
  350. case CURLMOPT_MAX_PIPELINE_LENGTH:
  351. case CURLMOPT_MAX_TOTAL_CONNECTIONS:
  352. #endif
  353. {
  354. zend_long lval = zval_get_long(zvalue);
  355. if (option == CURLMOPT_PIPELINING && (lval & 1)) {
  356. #if LIBCURL_VERSION_NUM >= 0x073e00 /* 7.62.0 */
  357. php_error_docref(NULL, E_WARNING, "CURLPIPE_HTTP1 is no longer supported");
  358. #else
  359. php_error_docref(NULL, E_DEPRECATED, "CURLPIPE_HTTP1 is deprecated");
  360. #endif
  361. }
  362. error = curl_multi_setopt(mh->multi, option, lval);
  363. break;
  364. }
  365. #if LIBCURL_VERSION_NUM > 0x072D00 /* Available since 7.45.0 */
  366. case CURLMOPT_PUSHFUNCTION:
  367. if (mh->handlers.server_push == NULL) {
  368. mh->handlers.server_push = ecalloc(1, sizeof(php_curl_callback));
  369. } else if (!Z_ISUNDEF(mh->handlers.server_push->func_name)) {
  370. zval_ptr_dtor(&mh->handlers.server_push->func_name);
  371. mh->handlers.server_push->fci_cache = empty_fcall_info_cache;
  372. }
  373. ZVAL_COPY(&mh->handlers.server_push->func_name, zvalue);
  374. error = curl_multi_setopt(mh->multi, option, _php_server_push_callback);
  375. if (error != CURLM_OK) {
  376. return 0;
  377. }
  378. error = curl_multi_setopt(mh->multi, CURLMOPT_PUSHDATA, mh);
  379. break;
  380. #endif
  381. default:
  382. zend_argument_value_error(2, "is not a valid cURL multi option");
  383. error = CURLM_UNKNOWN_OPTION;
  384. break;
  385. }
  386. SAVE_CURLM_ERROR(mh, error);
  387. return error != CURLM_OK;
  388. }
  389. /* }}} */
  390. /* {{{ Set an option for the curl multi handle */
  391. PHP_FUNCTION(curl_multi_setopt)
  392. {
  393. zval *z_mh, *zvalue;
  394. zend_long options;
  395. php_curlm *mh;
  396. ZEND_PARSE_PARAMETERS_START(3,3)
  397. Z_PARAM_OBJECT_OF_CLASS(z_mh, curl_multi_ce)
  398. Z_PARAM_LONG(options)
  399. Z_PARAM_ZVAL(zvalue)
  400. ZEND_PARSE_PARAMETERS_END();
  401. mh = Z_CURL_MULTI_P(z_mh);
  402. if (!_php_curl_multi_setopt(mh, options, zvalue, return_value)) {
  403. RETURN_TRUE;
  404. } else {
  405. RETURN_FALSE;
  406. }
  407. }
  408. /* }}} */
  409. /* CurlMultiHandle class */
  410. static zend_object_handlers curl_multi_handlers;
  411. static zend_object *curl_multi_create_object(zend_class_entry *class_type) {
  412. php_curlm *intern = zend_object_alloc(sizeof(php_curlm), class_type);
  413. zend_object_std_init(&intern->std, class_type);
  414. object_properties_init(&intern->std, class_type);
  415. intern->std.handlers = &curl_multi_handlers;
  416. return &intern->std;
  417. }
  418. static zend_function *curl_multi_get_constructor(zend_object *object) {
  419. zend_throw_error(NULL, "Cannot directly construct CurlMultiHandle, use curl_multi_init() instead");
  420. return NULL;
  421. }
  422. void curl_multi_free_obj(zend_object *object)
  423. {
  424. php_curlm *mh = curl_multi_from_obj(object);
  425. zend_llist_position pos;
  426. php_curl *ch;
  427. zval *pz_ch;
  428. if (!mh->multi) {
  429. /* Can happen if constructor throws. */
  430. zend_object_std_dtor(&mh->std);
  431. return;
  432. }
  433. for (pz_ch = (zval *)zend_llist_get_first_ex(&mh->easyh, &pos); pz_ch;
  434. pz_ch = (zval *)zend_llist_get_next_ex(&mh->easyh, &pos)) {
  435. if (!(OBJ_FLAGS(Z_OBJ_P(pz_ch)) & IS_OBJ_FREE_CALLED)) {
  436. ch = Z_CURL_P(pz_ch);
  437. _php_curl_verify_handlers(ch, 0);
  438. }
  439. }
  440. curl_multi_cleanup(mh->multi);
  441. zend_llist_clean(&mh->easyh);
  442. if (mh->handlers.server_push) {
  443. zval_ptr_dtor(&mh->handlers.server_push->func_name);
  444. efree(mh->handlers.server_push);
  445. }
  446. zend_object_std_dtor(&mh->std);
  447. }
  448. static HashTable *curl_multi_get_gc(zend_object *object, zval **table, int *n)
  449. {
  450. php_curlm *curl_multi = curl_multi_from_obj(object);
  451. zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create();
  452. if (curl_multi->handlers.server_push) {
  453. zend_get_gc_buffer_add_zval(gc_buffer, &curl_multi->handlers.server_push->func_name);
  454. }
  455. zend_llist_position pos;
  456. for (zval *pz_ch = (zval *) zend_llist_get_first_ex(&curl_multi->easyh, &pos); pz_ch;
  457. pz_ch = (zval *) zend_llist_get_next_ex(&curl_multi->easyh, &pos)) {
  458. zend_get_gc_buffer_add_zval(gc_buffer, pz_ch);
  459. }
  460. zend_get_gc_buffer_use(gc_buffer, table, n);
  461. return zend_std_get_properties(object);
  462. }
  463. void curl_multi_register_handlers(void) {
  464. curl_multi_ce->create_object = curl_multi_create_object;
  465. memcpy(&curl_multi_handlers, &std_object_handlers, sizeof(zend_object_handlers));
  466. curl_multi_handlers.offset = XtOffsetOf(php_curlm, std);
  467. curl_multi_handlers.free_obj = curl_multi_free_obj;
  468. curl_multi_handlers.get_gc = curl_multi_get_gc;
  469. curl_multi_handlers.get_constructor = curl_multi_get_constructor;
  470. curl_multi_handlers.clone_obj = NULL;
  471. curl_multi_handlers.cast_object = curl_cast_object;
  472. curl_multi_handlers.compare = zend_objects_not_comparable;
  473. }