codename: client meltdown
[m6w6/ext-http] / php_http_client_curl.c
1 /*
2 +--------------------------------------------------------------------+
3 | PECL :: http |
4 +--------------------------------------------------------------------+
5 | Redistribution and use in source and binary forms, with or without |
6 | modification, are permitted provided that the conditions mentioned |
7 | in the accompanying LICENSE file are met. |
8 +--------------------------------------------------------------------+
9 | Copyright (c) 2004-2011, Michael Wallner <mike@php.net> |
10 +--------------------------------------------------------------------+
11 */
12
13 #include "php_http_api.h"
14 #include "php_http_client.h"
15
16 #if PHP_HTTP_HAVE_CURL
17
18 #if PHP_HTTP_HAVE_EVENT
19 # include <event.h>
20 # if !PHP_HTTP_HAVE_EVENT2 && /* just be really sure */ !(LIBEVENT_VERSION_NUMBER >= 0x02000000)
21 # define event_base_new event_init
22 # define event_assign(e, b, s, a, cb, d) do {\
23 event_set(e, s, a, cb, d); \
24 event_base_set(b, e); \
25 } while(0)
26 # endif
27 # ifndef DBG_EVENTS
28 # define DBG_EVENTS 0
29 # endif
30 #endif
31
32 typedef struct php_http_client_curl {
33 CURLM *handle;
34
35 int unfinished; /* int because of curl_multi_perform() */
36
37 #if PHP_HTTP_HAVE_EVENT
38 struct event *timeout;
39 unsigned useevents:1;
40 #endif
41 } php_http_client_curl_t;
42
43 typedef struct php_http_client_curl_handler {
44 CURL *handle;
45 php_resource_factory_t *rf;
46 php_http_client_t *client;
47 php_http_client_progress_state_t progress;
48
49 php_http_client_enqueue_t queue;
50
51 struct {
52 php_http_message_parser_t *parser;
53 php_http_message_t *message;
54 php_http_buffer_t *buffer;
55 } request;
56
57 struct {
58 php_http_message_parser_t *parser;
59 php_http_message_t *message;
60 php_http_buffer_t *buffer;
61 } response;
62
63 struct {
64 HashTable cache;
65
66 struct curl_slist *headers;
67 struct curl_slist *resolve;
68 php_http_buffer_t cookies;
69 php_http_buffer_t ranges;
70
71 long redirects;
72 unsigned range_request:1;
73 unsigned encode_cookies:1;
74
75 struct {
76 uint count;
77 double delay;
78 } retry;
79
80 } options;
81
82 } php_http_client_curl_handler_t;
83
84 typedef struct php_http_curle_storage {
85 char *url;
86 char *cookiestore;
87 char errorbuffer[0x100];
88 } php_http_curle_storage_t;
89
90 static inline php_http_curle_storage_t *php_http_curle_get_storage(CURL *ch) {
91 php_http_curle_storage_t *st = NULL;
92
93 curl_easy_getinfo(ch, CURLINFO_PRIVATE, &st);
94
95 if (!st) {
96 st = pecalloc(1, sizeof(*st), 1);
97 curl_easy_setopt(ch, CURLOPT_PRIVATE, st);
98 curl_easy_setopt(ch, CURLOPT_ERRORBUFFER, st->errorbuffer);
99 }
100
101 return st;
102 }
103
104 static void *php_http_curle_ctor(void *opaque, void *init_arg TSRMLS_DC)
105 {
106 void *ch;
107
108 if ((ch = curl_easy_init())) {
109 php_http_curle_get_storage(ch);
110 return ch;
111 }
112 return NULL;
113 }
114
115 static void *php_http_curle_copy(void *opaque, void *handle TSRMLS_DC)
116 {
117 void *ch;
118
119 if ((ch = curl_easy_duphandle(handle))) {
120 curl_easy_reset(ch);
121 php_http_curle_get_storage(ch);
122 return ch;
123 }
124 return NULL;
125 }
126
127 static void php_http_curle_dtor(void *opaque, void *handle TSRMLS_DC)
128 {
129 php_http_curle_storage_t *st = php_http_curle_get_storage(handle);
130
131 curl_easy_cleanup(handle);
132
133 if (st) {
134 if (st->url) {
135 pefree(st->url, 1);
136 }
137 if (st->cookiestore) {
138 pefree(st->cookiestore, 1);
139 }
140 pefree(st, 1);
141 }
142 }
143
144 static php_resource_factory_ops_t php_http_curle_resource_factory_ops = {
145 php_http_curle_ctor,
146 php_http_curle_copy,
147 php_http_curle_dtor
148 };
149
150 static void *php_http_curlm_ctor(void *opaque, void *init_arg TSRMLS_DC)
151 {
152 return curl_multi_init();
153 }
154
155 static void php_http_curlm_dtor(void *opaque, void *handle TSRMLS_DC)
156 {
157 curl_multi_cleanup(handle);
158 }
159
160 static php_resource_factory_ops_t php_http_curlm_resource_factory_ops = {
161 php_http_curlm_ctor,
162 NULL,
163 php_http_curlm_dtor
164 };
165
166 /* curl callbacks */
167
168 static size_t php_http_curle_read_callback(void *data, size_t len, size_t n, void *ctx)
169 {
170 php_http_message_body_t *body = ctx;
171
172 if (body) {
173 TSRMLS_FETCH_FROM_CTX(body->ts);
174 return php_stream_read(php_http_message_body_stream(body), data, len * n);
175 }
176 return 0;
177 }
178
179 static int php_http_curle_progress_callback(void *ctx, double dltotal, double dlnow, double ultotal, double ulnow)
180 {
181 php_http_client_curl_handler_t *h = ctx;
182 zend_bool update = 0;
183
184 if (h->progress.dl.total != dltotal
185 || h->progress.dl.now != dlnow
186 || h->progress.ul.total != ultotal
187 || h->progress.ul.now != ulnow
188 ) {
189 update = 1;
190
191 h->progress.dl.total = dltotal;
192 h->progress.dl.now = dlnow;
193 h->progress.ul.total = ultotal;
194 h->progress.ul.now = ulnow;
195 }
196
197 if (update && h->client->callback.progress.func) {
198 h->client->callback.progress.func(h->client->callback.progress.arg, h->client, &h->queue, &h->progress);
199 }
200
201 return 0;
202 }
203
204 static curlioerr php_http_curle_ioctl_callback(CURL *ch, curliocmd cmd, void *ctx)
205 {
206 php_http_message_body_t *body = ctx;
207
208 if (cmd != CURLIOCMD_RESTARTREAD) {
209 return CURLIOE_UNKNOWNCMD;
210 }
211
212 if (body) {
213 TSRMLS_FETCH_FROM_CTX(body->ts);
214
215 if (SUCCESS == php_stream_rewind(php_http_message_body_stream(body))) {
216 return CURLIOE_OK;
217 }
218 }
219
220 return CURLIOE_FAILRESTART;
221 }
222
223 static int php_http_curle_raw_callback(CURL *ch, curl_infotype type, char *data, size_t length, void *ctx)
224 {
225 php_http_client_curl_handler_t *h = ctx;
226 unsigned flags = 0;
227
228 /* catch progress */
229 switch (type) {
230 case CURLINFO_TEXT:
231 if (php_memnstr(data, ZEND_STRL("About to connect"), data + length)) {
232 h->progress.info = "resolve";
233 } else if (php_memnstr(data, ZEND_STRL("Trying"), data + length)) {
234 h->progress.info = "connect";
235 } else if (php_memnstr(data, ZEND_STRL("Found bundle for host"), data + length)) {
236 h->progress.info = "connect";
237 } else if (php_memnstr(data, ZEND_STRL("Connected"), data + length)) {
238 h->progress.info = "connected";
239 } else if (php_memnstr(data, ZEND_STRL("Re-using existing connection!"), data + length)) {
240 h->progress.info = "connected";
241 } else if (php_memnstr(data, ZEND_STRL("left intact"), data + length)) {
242 h->progress.info = "not disconnected";
243 } else if (php_memnstr(data, ZEND_STRL("closed"), data + length)) {
244 h->progress.info = "disconnected";
245 } else if (php_memnstr(data, ZEND_STRL("Issue another request"), data + length)) {
246 h->progress.info = "redirect";
247 } else if (php_memnstr(data, ZEND_STRL("Operation timed out"), data + length)) {
248 h->progress.info = "timeout";
249 } else {
250 h->progress.info = data;
251 }
252 if (h->client->callback.progress.func) {
253 h->client->callback.progress.func(h->client->callback.progress.arg, h->client, &h->queue, &h->progress);
254 }
255 break;
256 case CURLINFO_HEADER_OUT:
257 case CURLINFO_DATA_OUT:
258 case CURLINFO_SSL_DATA_OUT:
259 h->progress.info = "send";
260 break;
261 case CURLINFO_HEADER_IN:
262 case CURLINFO_DATA_IN:
263 case CURLINFO_SSL_DATA_IN:
264 h->progress.info = "receive";
265 break;
266 default:
267 break;
268 }
269 /* process data */
270 switch (type) {
271 case CURLINFO_HEADER_IN:
272 case CURLINFO_DATA_IN:
273 php_http_buffer_append(h->response.buffer, data, length);
274
275 if (h->options.redirects) {
276 flags |= PHP_HTTP_MESSAGE_PARSER_EMPTY_REDIRECTS;
277 }
278
279 if (PHP_HTTP_MESSAGE_PARSER_STATE_FAILURE == php_http_message_parser_parse(h->response.parser, h->response.buffer, flags, &h->response.message)) {
280 return -1;
281 }
282 break;
283
284 case CURLINFO_HEADER_OUT:
285 case CURLINFO_DATA_OUT:
286 php_http_buffer_append(h->request.buffer, data, length);
287
288 if (PHP_HTTP_MESSAGE_PARSER_STATE_FAILURE == php_http_message_parser_parse(h->request.parser, h->request.buffer, flags, &h->request.message)) {
289 return -1;
290 }
291 break;
292 default:
293 break;
294 }
295
296 #if 0
297 /* debug */
298 _dpf(type, data, length);
299 #endif
300
301 return 0;
302 }
303
304 static int php_http_curle_dummy_callback(char *data, size_t n, size_t l, void *s)
305 {
306 return n*l;
307 }
308
309 static STATUS php_http_curle_get_info(CURL *ch, HashTable *info)
310 {
311 char *c;
312 long l;
313 double d;
314 struct curl_slist *s, *p;
315 zval *subarray, array;
316 INIT_PZVAL_ARRAY(&array, info);
317
318 /* BEGIN::CURLINFO */
319 if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_EFFECTIVE_URL, &c)) {
320 add_assoc_string_ex(&array, "effective_url", sizeof("effective_url"), c ? c : "", 1);
321 }
322 if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_RESPONSE_CODE, &l)) {
323 add_assoc_long_ex(&array, "response_code", sizeof("response_code"), l);
324 }
325 if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_TOTAL_TIME, &d)) {
326 add_assoc_double_ex(&array, "total_time", sizeof("total_time"), d);
327 }
328 if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_NAMELOOKUP_TIME, &d)) {
329 add_assoc_double_ex(&array, "namelookup_time", sizeof("namelookup_time"), d);
330 }
331 if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_CONNECT_TIME, &d)) {
332 add_assoc_double_ex(&array, "connect_time", sizeof("connect_time"), d);
333 }
334 if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_PRETRANSFER_TIME, &d)) {
335 add_assoc_double_ex(&array, "pretransfer_time", sizeof("pretransfer_time"), d);
336 }
337 if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_SIZE_UPLOAD, &d)) {
338 add_assoc_double_ex(&array, "size_upload", sizeof("size_upload"), d);
339 }
340 if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_SIZE_DOWNLOAD, &d)) {
341 add_assoc_double_ex(&array, "size_download", sizeof("size_download"), d);
342 }
343 if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_SPEED_DOWNLOAD, &d)) {
344 add_assoc_double_ex(&array, "speed_download", sizeof("speed_download"), d);
345 }
346 if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_SPEED_UPLOAD, &d)) {
347 add_assoc_double_ex(&array, "speed_upload", sizeof("speed_upload"), d);
348 }
349 if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_HEADER_SIZE, &l)) {
350 add_assoc_long_ex(&array, "header_size", sizeof("header_size"), l);
351 }
352 if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_REQUEST_SIZE, &l)) {
353 add_assoc_long_ex(&array, "request_size", sizeof("request_size"), l);
354 }
355 if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_SSL_VERIFYRESULT, &l)) {
356 add_assoc_long_ex(&array, "ssl_verifyresult", sizeof("ssl_verifyresult"), l);
357 }
358 if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_FILETIME, &l)) {
359 add_assoc_long_ex(&array, "filetime", sizeof("filetime"), l);
360 }
361 if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &d)) {
362 add_assoc_double_ex(&array, "content_length_download", sizeof("content_length_download"), d);
363 }
364 if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_CONTENT_LENGTH_UPLOAD, &d)) {
365 add_assoc_double_ex(&array, "content_length_upload", sizeof("content_length_upload"), d);
366 }
367 if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_STARTTRANSFER_TIME, &d)) {
368 add_assoc_double_ex(&array, "starttransfer_time", sizeof("starttransfer_time"), d);
369 }
370 if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_CONTENT_TYPE, &c)) {
371 add_assoc_string_ex(&array, "content_type", sizeof("content_type"), c ? c : "", 1);
372 }
373 if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_REDIRECT_TIME, &d)) {
374 add_assoc_double_ex(&array, "redirect_time", sizeof("redirect_time"), d);
375 }
376 if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_REDIRECT_COUNT, &l)) {
377 add_assoc_long_ex(&array, "redirect_count", sizeof("redirect_count"), l);
378 }
379 if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_HTTP_CONNECTCODE, &l)) {
380 add_assoc_long_ex(&array, "connect_code", sizeof("connect_code"), l);
381 }
382 if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_HTTPAUTH_AVAIL, &l)) {
383 add_assoc_long_ex(&array, "httpauth_avail", sizeof("httpauth_avail"), l);
384 }
385 if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_PROXYAUTH_AVAIL, &l)) {
386 add_assoc_long_ex(&array, "proxyauth_avail", sizeof("proxyauth_avail"), l);
387 }
388 if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_OS_ERRNO, &l)) {
389 add_assoc_long_ex(&array, "os_errno", sizeof("os_errno"), l);
390 }
391 if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_NUM_CONNECTS, &l)) {
392 add_assoc_long_ex(&array, "num_connects", sizeof("num_connects"), l);
393 }
394 if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_SSL_ENGINES, &s)) {
395 MAKE_STD_ZVAL(subarray);
396 array_init(subarray);
397 for (p = s; p; p = p->next) {
398 if (p->data) {
399 add_next_index_string(subarray, p->data, 1);
400 }
401 }
402 add_assoc_zval_ex(&array, "ssl_engines", sizeof("ssl_engines"), subarray);
403 curl_slist_free_all(s);
404 }
405 if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_COOKIELIST, &s)) {
406 MAKE_STD_ZVAL(subarray);
407 array_init(subarray);
408 for (p = s; p; p = p->next) {
409 if (p->data) {
410 add_next_index_string(subarray, p->data, 1);
411 }
412 }
413 add_assoc_zval_ex(&array, "cookies", sizeof("cookies"), subarray);
414 curl_slist_free_all(s);
415 }
416 if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_REDIRECT_URL, &c)) {
417 add_assoc_string_ex(&array, "redirect_url", sizeof("redirect_url"), c ? c : "", 1);
418 }
419 #if PHP_HTTP_CURL_VERSION(7,19,0)
420 if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_PRIMARY_IP, &c)) {
421 add_assoc_string_ex(&array, "primary_ip", sizeof("primary_ip"), c ? c : "", 1);
422 }
423 #endif
424 #if PHP_HTTP_CURL_VERSION(7,19,0)
425 if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_APPCONNECT_TIME, &d)) {
426 add_assoc_double_ex(&array, "appconnect_time", sizeof("appconnect_time"), d);
427 }
428 #endif
429 #if PHP_HTTP_CURL_VERSION(7,19,4)
430 if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_CONDITION_UNMET, &l)) {
431 add_assoc_long_ex(&array, "condition_unmet", sizeof("condition_unmet"), l);
432 }
433 #endif
434 #if PHP_HTTP_CURL_VERSION(7,21,0)
435 if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_PRIMARY_PORT, &l)) {
436 add_assoc_long_ex(&array, "primary_port", sizeof("primary_port"), l);
437 }
438 #endif
439 #if PHP_HTTP_CURL_VERSION(7,21,0)
440 if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_LOCAL_IP, &c)) {
441 add_assoc_string_ex(&array, "local_ip", sizeof("local_ip"), c ? c : "", 1);
442 }
443 #endif
444 #if PHP_HTTP_CURL_VERSION(7,21,0)
445 if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_LOCAL_PORT, &l)) {
446 add_assoc_long_ex(&array, "local_port", sizeof("local_port"), l);
447 }
448 #endif
449
450 /* END::CURLINFO */
451
452 #if PHP_HTTP_CURL_VERSION(7,19,1) && defined(PHP_HTTP_HAVE_OPENSSL)
453 {
454 int i;
455 zval *ci_array;
456 struct curl_certinfo *ci;
457 char *colon, *keyname;
458
459 if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_CERTINFO, &ci)) {
460 MAKE_STD_ZVAL(ci_array);
461 array_init(ci_array);
462
463 for (i = 0; i < ci->num_of_certs; ++i) {
464 s = ci->certinfo[i];
465
466 MAKE_STD_ZVAL(subarray);
467 array_init(subarray);
468 for (p = s; p; p = p->next) {
469 if (p->data) {
470 if ((colon = strchr(p->data, ':'))) {
471 keyname = estrndup(p->data, colon - p->data);
472 add_assoc_string_ex(subarray, keyname, colon - p->data + 1, colon + 1, 1);
473 efree(keyname);
474 } else {
475 add_next_index_string(subarray, p->data, 1);
476 }
477 }
478 }
479 add_next_index_zval(ci_array, subarray);
480 }
481 add_assoc_zval_ex(&array, "certinfo", sizeof("certinfo"), ci_array);
482 }
483 }
484 #endif
485 add_assoc_string_ex(&array, "error", sizeof("error"), php_http_curle_get_storage(ch)->errorbuffer, 1);
486
487 return SUCCESS;
488 }
489
490 static int compare_queue(php_http_client_enqueue_t *e, void *handle)
491 {
492 return handle == ((php_http_client_curl_handler_t *) e->opaque)->handle;
493 }
494
495 static void php_http_curlm_responsehandler(php_http_client_t *context)
496 {
497 int remaining = 0;
498 php_http_client_enqueue_t *enqueue;
499 php_http_client_curl_t *curl = context->ctx;
500 TSRMLS_FETCH_FROM_CTX(context->ts);
501
502 do {
503 CURLMsg *msg = curl_multi_info_read(curl->handle, &remaining);
504
505 if (msg && CURLMSG_DONE == msg->msg) {
506 if (CURLE_OK != msg->data.result) {
507 php_http_curle_storage_t *st = php_http_curle_get_storage(msg->easy_handle);
508 php_http_error(HE_WARNING, PHP_HTTP_E_CLIENT, "%s; %s (%s)", curl_easy_strerror(msg->data.result), STR_PTR(st->errorbuffer), STR_PTR(st->url));
509 }
510
511 if ((enqueue = php_http_client_enqueued(context, msg->easy_handle, compare_queue))) {
512 php_http_client_curl_handler_t *handler = enqueue->opaque;
513
514 context->callback.response.func(context->callback.response.arg, context, &handler->queue, &handler->request.message, &handler->response.message);
515 }
516 }
517 } while (remaining);
518 }
519
520 #if PHP_HTTP_HAVE_EVENT
521
522 typedef struct php_http_curlm_event {
523 struct event evnt;
524 php_http_client_t *context;
525 } php_http_curlm_event_t;
526
527 static inline int etoca(short action) {
528 switch (action & (EV_READ|EV_WRITE)) {
529 case EV_READ:
530 return CURL_CSELECT_IN;
531 break;
532 case EV_WRITE:
533 return CURL_CSELECT_OUT;
534 break;
535 case EV_READ|EV_WRITE:
536 return CURL_CSELECT_IN|CURL_CSELECT_OUT;
537 break;
538 default:
539 return 0;
540 }
541 }
542
543 static void php_http_curlm_timeout_callback(int socket, short action, void *event_data)
544 {
545 php_http_client_t *context = event_data;
546 php_http_client_curl_t *curl = context->ctx;
547
548 #if DBG_EVENTS
549 fprintf(stderr, "T");
550 #endif
551 if (curl->useevents) {
552 CURLMcode rc;
553 TSRMLS_FETCH_FROM_CTX(context->ts);
554
555 /* ignore and use -1,0 on timeout */
556 (void) socket;
557 (void) action;
558
559 while (CURLM_CALL_MULTI_PERFORM == (rc = curl_multi_socket_action(curl->handle, CURL_SOCKET_TIMEOUT, 0, &curl->unfinished)));
560
561 if (CURLM_OK != rc) {
562 php_http_error(HE_WARNING, PHP_HTTP_E_SOCKET, "%s", curl_multi_strerror(rc));
563 }
564
565 php_http_curlm_responsehandler(context);
566 }
567 }
568
569 static void php_http_curlm_event_callback(int socket, short action, void *event_data)
570 {
571 php_http_client_t *context = event_data;
572 php_http_client_curl_t *curl = context->ctx;
573
574 #if DBG_EVENTS
575 fprintf(stderr, "E");
576 #endif
577 if (curl->useevents) {
578 CURLMcode rc = CURLE_OK;
579 TSRMLS_FETCH_FROM_CTX(context->ts);
580
581 while (CURLM_CALL_MULTI_PERFORM == (rc = curl_multi_socket_action(curl->handle, socket, etoca(action), &curl->unfinished)));
582
583 if (CURLM_OK != rc) {
584 php_http_error(HE_WARNING, PHP_HTTP_E_SOCKET, "%s", curl_multi_strerror(rc));
585 }
586
587 php_http_curlm_responsehandler(context);
588
589 /* remove timeout if there are no transfers left */
590 if (!curl->unfinished && event_initialized(curl->timeout) && event_pending(curl->timeout, EV_TIMEOUT, NULL)) {
591 event_del(curl->timeout);
592 }
593 }
594 }
595
596 static int php_http_curlm_socket_callback(CURL *easy, curl_socket_t sock, int action, void *socket_data, void *assign_data)
597 {
598 php_http_client_t *context = socket_data;
599 php_http_client_curl_t *curl = context->ctx;
600
601 #if DBG_EVENTS
602 fprintf(stderr, "S");
603 #endif
604 if (curl->useevents) {
605 int events = EV_PERSIST;
606 php_http_curlm_event_t *ev = assign_data;
607 TSRMLS_FETCH_FROM_CTX(context->ts);
608
609 if (!ev) {
610 ev = ecalloc(1, sizeof(php_http_curlm_event_t));
611 ev->context = context;
612 curl_multi_assign(curl->handle, sock, ev);
613 } else {
614 event_del(&ev->evnt);
615 }
616
617 switch (action) {
618 case CURL_POLL_IN:
619 events |= EV_READ;
620 break;
621 case CURL_POLL_OUT:
622 events |= EV_WRITE;
623 break;
624 case CURL_POLL_INOUT:
625 events |= EV_READ|EV_WRITE;
626 break;
627
628 case CURL_POLL_REMOVE:
629 efree(ev);
630 /* no break */
631 case CURL_POLL_NONE:
632 return 0;
633
634 default:
635 php_http_error(HE_WARNING, PHP_HTTP_E_SOCKET, "Unknown socket action %d", action);
636 return -1;
637 }
638
639 event_assign(&ev->evnt, PHP_HTTP_G->curl.event_base, sock, events, php_http_curlm_event_callback, context);
640 event_add(&ev->evnt, NULL);
641 }
642
643 return 0;
644 }
645
646 static void php_http_curlm_timer_callback(CURLM *multi, long timeout_ms, void *timer_data)
647 {
648 php_http_client_t *context = timer_data;
649 php_http_client_curl_t *curl = context->ctx;
650
651 #if DBG_EVENTS
652 fprintf(stderr, "\ntimer <- timeout_ms: %ld\n", timeout_ms);
653 #endif
654 if (curl->useevents) {
655
656 if (timeout_ms < 0) {
657 php_http_curlm_timeout_callback(CURL_SOCKET_TIMEOUT, /*EV_READ|EV_WRITE*/0, context);
658 } else if (timeout_ms > 0 || !event_initialized(curl->timeout) || !event_pending(curl->timeout, EV_TIMEOUT, NULL)) {
659 struct timeval timeout;
660 TSRMLS_FETCH_FROM_CTX(context->ts);
661
662 if (!event_initialized(curl->timeout)) {
663 event_assign(curl->timeout, PHP_HTTP_G->curl.event_base, CURL_SOCKET_TIMEOUT, 0, php_http_curlm_timeout_callback, context);
664 } else if (event_pending(curl->timeout, EV_TIMEOUT, NULL)) {
665 event_del(curl->timeout);
666 }
667
668 timeout.tv_sec = timeout_ms / 1000;
669 timeout.tv_usec = (timeout_ms % 1000) * 1000;
670
671 event_add(curl->timeout, &timeout);
672 }
673 }
674 }
675
676 #endif /* HAVE_EVENT */
677
678 /* curl options */
679
680 static php_http_options_t php_http_curle_options;
681
682 #define PHP_HTTP_CURLE_OPTION_CHECK_STRLEN 0x0001
683 #define PHP_HTTP_CURLE_OPTION_CHECK_BASEDIR 0x0002
684 #define PHP_HTTP_CURLE_OPTION_TRANSFORM_MS 0x0004
685
686 static STATUS php_http_curle_option_set_ssl_verifyhost(php_http_option_t *opt, zval *val, void *userdata)
687 {
688 php_http_client_curl_handler_t *curl = userdata;
689 CURL *ch = curl->handle;
690
691 if (CURLE_OK != curl_easy_setopt(ch, CURLOPT_SSL_VERIFYHOST, Z_BVAL_P(val) ? 2 : 0)) {
692 return FAILURE;
693 }
694 return SUCCESS;
695 }
696
697 static STATUS php_http_curle_option_set_cookiestore(php_http_option_t *opt, zval *val, void *userdata)
698 {
699 php_http_client_curl_handler_t *curl = userdata;
700 CURL *ch = curl->handle;
701
702 if (val) {
703 php_http_curle_storage_t *storage = php_http_curle_get_storage(curl->handle);
704
705 if (storage->cookiestore) {
706 pefree(storage->cookiestore, 1);
707 }
708 storage->cookiestore = pestrndup(Z_STRVAL_P(val), Z_STRLEN_P(val), 1);
709 if ( CURLE_OK != curl_easy_setopt(ch, CURLOPT_COOKIEFILE, storage->cookiestore)
710 || CURLE_OK != curl_easy_setopt(ch, CURLOPT_COOKIEJAR, storage->cookiestore)
711 ) {
712 return FAILURE;
713 }
714 }
715 return SUCCESS;
716 }
717
718 static STATUS php_http_curle_option_set_cookies(php_http_option_t *opt, zval *val, void *userdata)
719 {
720 php_http_client_curl_handler_t *curl = userdata;
721 CURL *ch = curl->handle;
722 TSRMLS_FETCH_FROM_CTX(curl->client->ts);
723
724 if (val && Z_TYPE_P(val) != IS_NULL) {
725 if (curl->options.encode_cookies) {
726 if (SUCCESS == php_http_url_encode_hash_ex(HASH_OF(val), &curl->options.cookies, ZEND_STRL(";"), ZEND_STRL("="), NULL, 0 TSRMLS_CC)) {
727 php_http_buffer_fix(&curl->options.cookies);
728 if (CURLE_OK != curl_easy_setopt(ch, CURLOPT_COOKIE, curl->options.cookies.data)) {
729 return FAILURE;
730 }
731 } else {
732 return FAILURE;
733 }
734 } else {
735 HashPosition pos;
736 php_http_array_hashkey_t cookie_key = php_http_array_hashkey_init(0);
737 zval **cookie_val;
738
739 FOREACH_KEYVAL(pos, val, cookie_key, cookie_val) {
740 zval *zv = php_http_ztyp(IS_STRING, *cookie_val);
741
742 php_http_array_hashkey_stringify(&cookie_key);
743 php_http_buffer_appendf(&curl->options.cookies, "%s=%s; ", cookie_key.str, Z_STRVAL_P(zv));
744 php_http_array_hashkey_stringfree(&cookie_key);
745
746 zval_ptr_dtor(&zv);
747 }
748
749 php_http_buffer_fix(&curl->options.cookies);
750 if (curl->options.cookies.used) {
751 if (CURLE_OK != curl_easy_setopt(ch, CURLOPT_COOKIE, curl->options.cookies.data)) {
752 return FAILURE;
753 }
754 }
755 }
756 }
757 return SUCCESS;
758 }
759
760 static STATUS php_http_curle_option_set_encodecookies(php_http_option_t *opt, zval *val, void *userdata)
761 {
762 php_http_client_curl_handler_t *curl = userdata;
763
764 curl->options.encode_cookies = Z_BVAL_P(val);
765 return SUCCESS;
766 }
767
768 static STATUS php_http_curle_option_set_lastmodified(php_http_option_t *opt, zval *val, void *userdata)
769 {
770 php_http_client_curl_handler_t *curl = userdata;
771 CURL *ch = curl->handle;
772 TSRMLS_FETCH_FROM_CTX(curl->client->ts);
773
774 if (Z_LVAL_P(val)) {
775 if (Z_LVAL_P(val) > 0) {
776 if (CURLE_OK != curl_easy_setopt(ch, CURLOPT_TIMEVALUE, Z_LVAL_P(val))) {
777 return FAILURE;
778 }
779 } else {
780 if (CURLE_OK != curl_easy_setopt(ch, CURLOPT_TIMEVALUE, (long) PHP_HTTP_G->env.request.time + Z_LVAL_P(val))) {
781 return FAILURE;
782 }
783 }
784 if (CURLE_OK != curl_easy_setopt(ch, CURLOPT_TIMECONDITION, (long) (curl->options.range_request ? CURL_TIMECOND_IFUNMODSINCE : CURL_TIMECOND_IFMODSINCE))) {
785 return FAILURE;
786 }
787 } else {
788 if ( CURLE_OK != curl_easy_setopt(ch, CURLOPT_TIMEVALUE, 0)
789 || CURLE_OK != curl_easy_setopt(ch, CURLOPT_TIMECONDITION, 0)
790 ) {
791 return FAILURE;
792 }
793 }
794 return SUCCESS;
795 }
796
797 static STATUS php_http_curle_option_set_compress(php_http_option_t *opt, zval *val, void *userdata)
798 {
799 php_http_client_curl_handler_t *curl = userdata;
800
801 if (Z_BVAL_P(val)) {
802 curl->options.headers = curl_slist_append(curl->options.headers, "Accept-Encoding: gzip;q=1.0,deflate;q=0.5");
803 }
804 return SUCCESS;
805 }
806
807 static STATUS php_http_curle_option_set_etag(php_http_option_t *opt, zval *val, void *userdata)
808 {
809 php_http_client_curl_handler_t *curl = userdata;
810 php_http_buffer_t header;
811 zend_bool is_quoted = !((Z_STRVAL_P(val)[0] != '"') || (Z_STRVAL_P(val)[Z_STRLEN_P(val)-1] != '"'));
812
813 php_http_buffer_init(&header);
814 php_http_buffer_appendf(&header, is_quoted?"%s: %s":"%s: \"%s\"", curl->options.range_request?"If-Match":"If-None-Match", Z_STRVAL_P(val));
815 php_http_buffer_fix(&header);
816 curl->options.headers = curl_slist_append(curl->options.headers, header.data);
817 php_http_buffer_dtor(&header);
818 return SUCCESS;
819 }
820
821 static STATUS php_http_curle_option_set_range(php_http_option_t *opt, zval *val, void *userdata)
822 {
823 php_http_client_curl_handler_t *curl = userdata;
824 CURL *ch = curl->handle;
825 TSRMLS_FETCH_FROM_CTX(curl->client->ts);
826
827 php_http_buffer_reset(&curl->options.ranges);
828
829 if (val && Z_TYPE_P(val) != IS_NULL) {
830 HashPosition pos;
831 zval **rr, **rb, **re;
832
833 FOREACH_VAL(pos, val, rr) {
834 if (Z_TYPE_PP(rr) == IS_ARRAY) {
835 if (2 == php_http_array_list(Z_ARRVAL_PP(rr) TSRMLS_CC, 2, &rb, &re)) {
836 if ( ((Z_TYPE_PP(rb) == IS_LONG) || ((Z_TYPE_PP(rb) == IS_STRING) && is_numeric_string(Z_STRVAL_PP(rb), Z_STRLEN_PP(rb), NULL, NULL, 1))) &&
837 ((Z_TYPE_PP(re) == IS_LONG) || ((Z_TYPE_PP(re) == IS_STRING) && is_numeric_string(Z_STRVAL_PP(re), Z_STRLEN_PP(re), NULL, NULL, 1)))) {
838 zval *rbl = php_http_ztyp(IS_LONG, *rb);
839 zval *rel = php_http_ztyp(IS_LONG, *re);
840
841 if ((Z_LVAL_P(rbl) >= 0) && (Z_LVAL_P(rel) >= 0)) {
842 php_http_buffer_appendf(&curl->options.ranges, "%ld-%ld,", Z_LVAL_P(rbl), Z_LVAL_P(rel));
843 }
844 zval_ptr_dtor(&rbl);
845 zval_ptr_dtor(&rel);
846 }
847
848 }
849 }
850 }
851
852 if (curl->options.ranges.used) {
853 curl->options.range_request = 1;
854 /* ditch last comma */
855 curl->options.ranges.data[curl->options.ranges.used - 1] = '\0';
856 }
857 }
858
859 if (CURLE_OK != curl_easy_setopt(ch, CURLOPT_RANGE, curl->options.ranges.data)) {
860 return FAILURE;
861 }
862 return SUCCESS;
863 }
864
865 static STATUS php_http_curle_option_set_resume(php_http_option_t *opt, zval *val, void *userdata)
866 {
867 php_http_client_curl_handler_t *curl = userdata;
868 CURL *ch = curl->handle;
869
870 if (Z_LVAL_P(val) > 0) {
871 curl->options.range_request = 1;
872 }
873 if (CURLE_OK != curl_easy_setopt(ch, CURLOPT_RESUME_FROM, Z_LVAL_P(val))) {
874 return FAILURE;
875 }
876 return SUCCESS;
877 }
878
879 static STATUS php_http_curle_option_set_retrydelay(php_http_option_t *opt, zval *val, void *userdata)
880 {
881 php_http_client_curl_handler_t *curl = userdata;
882
883 curl->options.retry.delay = Z_DVAL_P(val);
884 return SUCCESS;
885 }
886
887 static STATUS php_http_curle_option_set_retrycount(php_http_option_t *opt, zval *val, void *userdata)
888 {
889 php_http_client_curl_handler_t *curl = userdata;
890
891 curl->options.retry.count = Z_LVAL_P(val);
892 return SUCCESS;
893 }
894
895 static STATUS php_http_curle_option_set_redirect(php_http_option_t *opt, zval *val, void *userdata)
896 {
897 php_http_client_curl_handler_t *curl = userdata;
898 CURL *ch = curl->handle;
899
900 if ( CURLE_OK != curl_easy_setopt(ch, CURLOPT_FOLLOWLOCATION, Z_LVAL_P(val) ? 1L : 0L)
901 || CURLE_OK != curl_easy_setopt(ch, CURLOPT_MAXREDIRS, curl->options.redirects = Z_LVAL_P(val))
902 ) {
903 return FAILURE;
904 }
905 return SUCCESS;
906 }
907
908 static STATUS php_http_curle_option_set_portrange(php_http_option_t *opt, zval *val, void *userdata)
909 {
910 php_http_client_curl_handler_t *curl = userdata;
911 CURL *ch = curl->handle;
912 long localport = 0, localportrange = 0;
913 TSRMLS_FETCH_FROM_CTX(curl->client->ts);
914
915 if (val && Z_TYPE_P(val) != IS_NULL) {
916 zval **z_port_start, *zps_copy = NULL, **z_port_end, *zpe_copy = NULL;
917
918 switch (php_http_array_list(Z_ARRVAL_P(val) TSRMLS_CC, 2, &z_port_start, &z_port_end)) {
919 case 2:
920 zps_copy = php_http_ztyp(IS_LONG, *z_port_start);
921 zpe_copy = php_http_ztyp(IS_LONG, *z_port_end);
922 localportrange = labs(Z_LVAL_P(zps_copy)-Z_LVAL_P(zpe_copy))+1L;
923 /* no break */
924 case 1:
925 if (!zps_copy) {
926 zps_copy = php_http_ztyp(IS_LONG, *z_port_start);
927 }
928 localport = (zpe_copy && Z_LVAL_P(zpe_copy) > 0) ? MIN(Z_LVAL_P(zps_copy), Z_LVAL_P(zpe_copy)) : Z_LVAL_P(zps_copy);
929 zval_ptr_dtor(&zps_copy);
930 if (zpe_copy) {
931 zval_ptr_dtor(&zpe_copy);
932 }
933 break;
934 default:
935 break;
936 }
937 }
938 if ( CURLE_OK != curl_easy_setopt(ch, CURLOPT_LOCALPORT, localport)
939 || CURLE_OK != curl_easy_setopt(ch, CURLOPT_LOCALPORTRANGE, localportrange)
940 ) {
941 return FAILURE;
942 }
943 return SUCCESS;
944 }
945
946 #if PHP_HTTP_CURL_VERSION(7,21,3)
947 static STATUS php_http_curle_option_set_resolve(php_http_option_t *opt, zval *val, void *userdata)
948 {
949 php_http_client_curl_handler_t *curl = userdata;
950 CURL *ch = curl->handle;
951 TSRMLS_FETCH_FROM_CTX(curl->client->ts);
952
953 if (val && Z_TYPE_P(val) != IS_NULL) {
954 php_http_array_hashkey_t key = php_http_array_hashkey_init(0);
955 HashPosition pos;
956 zval **data;
957
958 FOREACH_KEYVAL(pos, val, key, data) {
959 zval *cpy = php_http_ztyp(IS_STRING, *data);
960 curl->options.resolve = curl_slist_append(curl->options.resolve, Z_STRVAL_P(cpy));
961 zval_ptr_dtor(&cpy);
962 }
963
964 if (CURLE_OK != curl_easy_setopt(ch, CURLOPT_RESOLVE, curl->options.resolve)) {
965 return FAILURE;
966 }
967 } else {
968 if (CURLE_OK != curl_easy_setopt(ch, CURLOPT_RESOLVE, NULL)) {
969 return FAILURE;
970 }
971 }
972 return SUCCESS;
973 }
974 #endif
975
976 static void php_http_curle_options_init(php_http_options_t *registry TSRMLS_DC)
977 {
978 php_http_option_t *opt;
979
980 /* proxy */
981 if ((opt = php_http_option_register(registry, ZEND_STRL("proxyhost"), CURLOPT_PROXY, IS_STRING))) {
982 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN;
983 }
984 php_http_option_register(registry, ZEND_STRL("proxytype"), CURLOPT_PROXYTYPE, IS_LONG);
985 php_http_option_register(registry, ZEND_STRL("proxyport"), CURLOPT_PROXYPORT, IS_LONG);
986 if ((opt = php_http_option_register(registry, ZEND_STRL("proxyauth"), CURLOPT_PROXYUSERPWD, IS_STRING))) {
987 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN;
988 }
989 php_http_option_register(registry, ZEND_STRL("proxyauthtype"), CURLOPT_PROXYAUTH, IS_LONG);
990 php_http_option_register(registry, ZEND_STRL("proxytunnel"), CURLOPT_HTTPPROXYTUNNEL, IS_BOOL);
991 #if PHP_HTTP_CURL_VERSION(7,19,4)
992 php_http_option_register(registry, ZEND_STRL("noproxy"), CURLOPT_NOPROXY, IS_STRING);
993 #endif
994
995 /* dns */
996 if ((opt = php_http_option_register(registry, ZEND_STRL("dns_cache_timeout"), CURLOPT_DNS_CACHE_TIMEOUT, IS_LONG))) {
997 Z_LVAL(opt->defval) = 60;
998 }
999 php_http_option_register(registry, ZEND_STRL("ipresolve"), CURLOPT_IPRESOLVE, IS_LONG);
1000 #if PHP_HTTP_CURL_VERSION(7,21,3)
1001 if ((opt = php_http_option_register(registry, ZEND_STRL("resolve"), CURLOPT_RESOLVE, IS_ARRAY))) {
1002 opt->setter = php_http_curle_option_set_resolve;
1003 }
1004 #endif
1005 #if PHP_HTTP_CURL_VERSION(7,24,0)
1006 if ((opt = php_http_option_register(registry, ZEND_STRL("dns_servers"), CURLOPT_DNS_SERVERS, IS_STRING))) {
1007 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN;
1008 }
1009 #endif
1010
1011 /* limits */
1012 php_http_option_register(registry, ZEND_STRL("low_speed_limit"), CURLOPT_LOW_SPEED_LIMIT, IS_LONG);
1013 php_http_option_register(registry, ZEND_STRL("low_speed_time"), CURLOPT_LOW_SPEED_TIME, IS_LONG);
1014
1015 /* LSF weirdance
1016 php_http_option_register(registry, ZEND_STRL("max_send_speed"), CURLOPT_MAX_SEND_SPEED_LARGE, IS_LONG);
1017 php_http_option_register(registry, ZEND_STRL("max_recv_speed"), CURLOPT_MAX_RECV_SPEED_LARGE, IS_LONG);
1018 */
1019
1020 /* connection handling */
1021 /* crashes
1022 if ((opt = php_http_option_register(registry, ZEND_STRL("maxconnects"), CURLOPT_MAXCONNECTS, IS_LONG))) {
1023 Z_LVAL(opt->defval) = 5;
1024 }
1025 */
1026 php_http_option_register(registry, ZEND_STRL("fresh_connect"), CURLOPT_FRESH_CONNECT, IS_BOOL);
1027 php_http_option_register(registry, ZEND_STRL("forbid_reuse"), CURLOPT_FORBID_REUSE, IS_BOOL);
1028
1029 /* outgoing interface */
1030 php_http_option_register(registry, ZEND_STRL("interface"), CURLOPT_INTERFACE, IS_STRING);
1031 if ((opt = php_http_option_register(registry, ZEND_STRL("portrange"), CURLOPT_LOCALPORT, IS_ARRAY))) {
1032 opt->setter = php_http_curle_option_set_portrange;
1033 }
1034
1035 /* another endpoint port */
1036 php_http_option_register(registry, ZEND_STRL("port"), CURLOPT_PORT, IS_LONG);
1037
1038 /* RFC4007 zone_id */
1039 #if PHP_HTTP_CURL_VERSION(7,19,0)
1040 php_http_option_register(registry, ZEND_STRL("address_scope"), CURLOPT_ADDRESS_SCOPE, IS_LONG);
1041 #endif
1042
1043 /* auth */
1044 if ((opt = php_http_option_register(registry, ZEND_STRL("httpauth"), CURLOPT_USERPWD, IS_STRING))) {
1045 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN;
1046 }
1047 php_http_option_register(registry, ZEND_STRL("httpauthtype"), CURLOPT_HTTPAUTH, IS_LONG);
1048
1049 /* redirects */
1050 if ((opt = php_http_option_register(registry, ZEND_STRL("redirect"), CURLOPT_FOLLOWLOCATION, IS_LONG))) {
1051 opt->setter = php_http_curle_option_set_redirect;
1052 }
1053 php_http_option_register(registry, ZEND_STRL("unrestrictedauth"), CURLOPT_UNRESTRICTED_AUTH, IS_BOOL);
1054 #if PHP_HTTP_CURL_VERSION(7,19,1)
1055 php_http_option_register(registry, ZEND_STRL("postredir"), CURLOPT_POSTREDIR, IS_BOOL);
1056 #else
1057 php_http_option_register(registry, ZEND_STRL("postredir"), CURLOPT_POST301, IS_BOOL);
1058 #endif
1059
1060 /* retries */
1061 if ((opt = php_http_option_register(registry, ZEND_STRL("retrycount"), 0, IS_LONG))) {
1062 opt->setter = php_http_curle_option_set_retrycount;
1063 }
1064 if ((opt = php_http_option_register(registry, ZEND_STRL("retrydelay"), 0, IS_DOUBLE))) {
1065 opt->setter = php_http_curle_option_set_retrydelay;
1066 }
1067
1068 /* referer */
1069 if ((opt = php_http_option_register(registry, ZEND_STRL("referer"), CURLOPT_REFERER, IS_STRING))) {
1070 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN;
1071 }
1072 if ((opt = php_http_option_register(registry, ZEND_STRL("autoreferer"), CURLOPT_AUTOREFERER, IS_BOOL))) {
1073 ZVAL_BOOL(&opt->defval, 1);
1074 }
1075
1076 /* useragent */
1077 if ((opt = php_http_option_register(registry, ZEND_STRL("useragent"), CURLOPT_USERAGENT, IS_STRING))) {
1078 /* don't check strlen, to allow sending no useragent at all */
1079 ZVAL_STRING(&opt->defval, "PECL::HTTP/" PHP_HTTP_EXT_VERSION " (PHP/" PHP_VERSION ")", 0);
1080 }
1081
1082 /* resume */
1083 if ((opt = php_http_option_register(registry, ZEND_STRL("resume"), CURLOPT_RESUME_FROM, IS_LONG))) {
1084 opt->setter = php_http_curle_option_set_resume;
1085 }
1086 /* ranges */
1087 if ((opt = php_http_option_register(registry, ZEND_STRL("range"), CURLOPT_RANGE, IS_ARRAY))) {
1088 opt->setter = php_http_curle_option_set_range;
1089 }
1090
1091 /* etag */
1092 if ((opt = php_http_option_register(registry, ZEND_STRL("etag"), 0, IS_STRING))) {
1093 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN;
1094 opt->setter = php_http_curle_option_set_etag;
1095 }
1096
1097 /* compression */
1098 if ((opt = php_http_option_register(registry, ZEND_STRL("compress"), 0, IS_BOOL))) {
1099 opt->setter = php_http_curle_option_set_compress;
1100 }
1101
1102 /* lastmodified */
1103 if ((opt = php_http_option_register(registry, ZEND_STRL("lastmodified"), 0, IS_LONG))) {
1104 opt->setter = php_http_curle_option_set_lastmodified;
1105 }
1106
1107 /* cookies */
1108 if ((opt = php_http_option_register(registry, ZEND_STRL("encodecookies"), 0, IS_BOOL))) {
1109 opt->setter = php_http_curle_option_set_encodecookies;
1110 ZVAL_BOOL(&opt->defval, 1);
1111 }
1112 if ((opt = php_http_option_register(registry, ZEND_STRL("cookies"), 0, IS_ARRAY))) {
1113 opt->setter = php_http_curle_option_set_cookies;
1114 }
1115
1116 /* cookiesession, don't load session cookies from cookiestore */
1117 php_http_option_register(registry, ZEND_STRL("cookiesession"), CURLOPT_COOKIESESSION, IS_BOOL);
1118 /* cookiestore, read initial cookies from that file and store cookies back into that file */
1119 if ((opt = php_http_option_register(registry, ZEND_STRL("cookiestore"), 0, IS_STRING))) {
1120 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN;
1121 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_BASEDIR;
1122 opt->setter = php_http_curle_option_set_cookiestore;
1123 }
1124
1125 /* maxfilesize */
1126 php_http_option_register(registry, ZEND_STRL("maxfilesize"), CURLOPT_MAXFILESIZE, IS_LONG);
1127
1128 /* http protocol version */
1129 php_http_option_register(registry, ZEND_STRL("protocol"), CURLOPT_HTTP_VERSION, IS_LONG);
1130
1131 /* timeouts */
1132 if ((opt = php_http_option_register(registry, ZEND_STRL("timeout"), CURLOPT_TIMEOUT_MS, IS_DOUBLE))) {
1133 opt->flags |= PHP_HTTP_CURLE_OPTION_TRANSFORM_MS;
1134 }
1135 if ((opt = php_http_option_register(registry, ZEND_STRL("connecttimeout"), CURLOPT_CONNECTTIMEOUT_MS, IS_DOUBLE))) {
1136 opt->flags |= PHP_HTTP_CURLE_OPTION_TRANSFORM_MS;
1137 Z_DVAL(opt->defval) = 3;
1138 }
1139
1140 /* tcp */
1141 #if PHP_HTTP_CURL_VERSION(7,25,0)
1142 php_http_option_register(registry, ZEND_STRL("tcp_keepalive"), CURLOPT_TCP_KEEPALIVE, IS_BOOL);
1143 if ((opt = php_http_option_register(registry, ZEND_STRL("tcp_keepidle"), CURLOPT_TCP_KEEPIDLE, IS_LONG))) {
1144 Z_LVAL(opt->defval) = 60;
1145 }
1146 if ((opt = php_http_option_register(registry, ZEND_STRL("tcp_keepintvl"), CURLOPT_TCP_KEEPINTVL, IS_LONG))) {
1147 Z_LVAL(opt->defval) = 60;
1148 }
1149 #endif
1150
1151 /* ssl */
1152 if ((opt = php_http_option_register(registry, ZEND_STRL("ssl"), 0, IS_ARRAY))) {
1153 registry = &opt->suboptions;
1154
1155 if ((opt = php_http_option_register(registry, ZEND_STRL("cert"), CURLOPT_SSLCERT, IS_STRING))) {
1156 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN;
1157 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_BASEDIR;
1158 }
1159 php_http_option_register(registry, ZEND_STRL("certtype"), CURLOPT_SSLCERTTYPE, IS_STRING);
1160 php_http_option_register(registry, ZEND_STRL("certpasswd"), CURLOPT_SSLCERTPASSWD, IS_STRING);
1161
1162 if ((opt = php_http_option_register(registry, ZEND_STRL("key"), CURLOPT_SSLKEY, IS_STRING))) {
1163 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN;
1164 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_BASEDIR;
1165 }
1166 php_http_option_register(registry, ZEND_STRL("keytype"), CURLOPT_SSLKEYTYPE, IS_STRING);
1167 php_http_option_register(registry, ZEND_STRL("keypasswd"), CURLOPT_SSLKEYPASSWD, IS_STRING);
1168 php_http_option_register(registry, ZEND_STRL("engine"), CURLOPT_SSLENGINE, IS_STRING);
1169 php_http_option_register(registry, ZEND_STRL("version"), CURLOPT_SSLVERSION, IS_LONG);
1170 if ((opt = php_http_option_register(registry, ZEND_STRL("verifypeer"), CURLOPT_SSL_VERIFYPEER, IS_BOOL))) {
1171 ZVAL_BOOL(&opt->defval, 1);
1172 }
1173 if ((opt = php_http_option_register(registry, ZEND_STRL("verifyhost"), CURLOPT_SSL_VERIFYHOST, IS_BOOL))) {
1174 ZVAL_BOOL(&opt->defval, 1);
1175 opt->setter = php_http_curle_option_set_ssl_verifyhost;
1176 }
1177 php_http_option_register(registry, ZEND_STRL("cipher_list"), CURLOPT_SSL_CIPHER_LIST, IS_STRING);
1178 if ((opt = php_http_option_register(registry, ZEND_STRL("cainfo"), CURLOPT_CAINFO, IS_STRING))) {
1179 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN;
1180 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_BASEDIR;
1181 #ifdef PHP_HTTP_CURL_CAINFO
1182 ZVAL_STRING(&opt->defval, PHP_HTTP_CURL_CAINFO, 0);
1183 #endif
1184 }
1185 if ((opt = php_http_option_register(registry, ZEND_STRL("capath"), CURLOPT_CAPATH, IS_STRING))) {
1186 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN;
1187 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_BASEDIR;
1188 }
1189 if ((opt = php_http_option_register(registry, ZEND_STRL("random_file"), CURLOPT_RANDOM_FILE, IS_STRING))) {
1190 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN;
1191 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_BASEDIR;
1192 }
1193 if ((opt = php_http_option_register(registry, ZEND_STRL("egdsocket"), CURLOPT_EGDSOCKET, IS_STRING))) {
1194 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN;
1195 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_BASEDIR;
1196 }
1197 #if PHP_HTTP_CURL_VERSION(7,19,0)
1198 if ((opt = php_http_option_register(registry, ZEND_STRL("issuercert"), CURLOPT_ISSUERCERT, IS_STRING))) {
1199 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN;
1200 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_BASEDIR;
1201 }
1202 # ifdef PHP_HTTP_HAVE_OPENSSL
1203 if ((opt = php_http_option_register(registry, ZEND_STRL("crlfile"), CURLOPT_CRLFILE, IS_STRING))) {
1204 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN;
1205 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_BASEDIR;
1206 }
1207 # endif
1208 #endif
1209 #if PHP_HTTP_CURL_VERSION(7,19,1) && defined(PHP_HTTP_HAVE_OPENSSL)
1210 php_http_option_register(registry, ZEND_STRL("certinfo"), CURLOPT_CERTINFO, IS_BOOL);
1211 #endif
1212 }
1213 }
1214
1215 static zval *php_http_curle_get_option(php_http_option_t *opt, HashTable *options, void *userdata)
1216 {
1217 php_http_client_curl_handler_t *curl = userdata;
1218 zval *option;
1219
1220 if ((option = php_http_option_get(opt, options, NULL))) {
1221 option = php_http_ztyp(opt->type, option);
1222 zend_hash_quick_update(&curl->options.cache, opt->name.s, opt->name.l, opt->name.h, &option, sizeof(zval *), NULL);
1223 }
1224 return option;
1225 }
1226
1227 static STATUS php_http_curle_set_option(php_http_option_t *opt, zval *val, void *userdata)
1228 {
1229 php_http_client_curl_handler_t *curl = userdata;
1230 CURL *ch = curl->handle;
1231 zval tmp;
1232 STATUS rv = SUCCESS;
1233 TSRMLS_FETCH_FROM_CTX(curl->client->ts);
1234
1235 if (!val) {
1236 val = &opt->defval;
1237 }
1238
1239 switch (opt->type) {
1240 case IS_BOOL:
1241 if (opt->setter) {
1242 rv = opt->setter(opt, val, curl);
1243 } else if (CURLE_OK != curl_easy_setopt(ch, opt->option, (long) Z_BVAL_P(val))) {
1244 rv = FAILURE;
1245 }
1246 break;
1247
1248 case IS_LONG:
1249 if (opt->setter) {
1250 rv = opt->setter(opt, val, curl);
1251 } else if (CURLE_OK != curl_easy_setopt(ch, opt->option, Z_LVAL_P(val))) {
1252 rv = FAILURE;
1253 }
1254 break;
1255
1256 case IS_STRING:
1257 if (!(opt->flags & PHP_HTTP_CURLE_OPTION_CHECK_STRLEN) || Z_STRLEN_P(val)) {
1258 if (!(opt->flags & PHP_HTTP_CURLE_OPTION_CHECK_BASEDIR) || !Z_STRVAL_P(val) || SUCCESS == php_check_open_basedir(Z_STRVAL_P(val) TSRMLS_CC)) {
1259 if (opt->setter) {
1260 rv = opt->setter(opt, val, curl);
1261 } else if (CURLE_OK != curl_easy_setopt(ch, opt->option, Z_STRVAL_P(val))) {
1262 rv = FAILURE;
1263 }
1264 }
1265 }
1266 break;
1267
1268 case IS_DOUBLE:
1269 if (opt->flags & PHP_HTTP_CURLE_OPTION_TRANSFORM_MS) {
1270 tmp = *val;
1271 Z_DVAL(tmp) *= 1000;
1272 val = &tmp;
1273 }
1274 if (opt->setter) {
1275 rv = opt->setter(opt, val, curl);
1276 } else if (CURLE_OK != curl_easy_setopt(ch, opt->option, (long) Z_DVAL_P(val))) {
1277 rv = FAILURE;
1278 }
1279 break;
1280
1281 case IS_ARRAY:
1282 if (opt->setter) {
1283 rv = opt->setter(opt, val, curl);
1284 } else if (Z_TYPE_P(val) != IS_NULL) {
1285 rv = php_http_options_apply(&opt->suboptions, Z_ARRVAL_P(val), curl);
1286 }
1287 break;
1288
1289 default:
1290 if (opt->setter) {
1291 rv = opt->setter(opt, val, curl);
1292 } else {
1293 rv = FAILURE;
1294 }
1295 break;
1296 }
1297 if (rv != SUCCESS) {
1298 php_http_error(HE_NOTICE, PHP_HTTP_E_CLIENT, "Could not set option %s", opt->name.s);
1299 }
1300 return rv;
1301 }
1302
1303
1304 /* client ops */
1305
1306 static STATUS php_http_client_curl_handler_reset(php_http_client_curl_handler_t *curl)
1307 {
1308 CURL *ch = curl->handle;
1309 php_http_curle_storage_t *st;
1310
1311 if ((st = php_http_curle_get_storage(ch))) {
1312 if (st->url) {
1313 pefree(st->url, 1);
1314 st->url = NULL;
1315 }
1316 if (st->cookiestore) {
1317 pefree(st->cookiestore, 1);
1318 st->cookiestore = NULL;
1319 }
1320 st->errorbuffer[0] = '\0';
1321 }
1322
1323 curl_easy_setopt(ch, CURLOPT_URL, NULL);
1324 /* libcurl < 7.19.6 does not clear auth info with USERPWD set to NULL */
1325 #if PHP_HTTP_CURL_VERSION(7,19,1)
1326 curl_easy_setopt(ch, CURLOPT_PROXYUSERNAME, NULL);
1327 curl_easy_setopt(ch, CURLOPT_PROXYPASSWORD, NULL);
1328 curl_easy_setopt(ch, CURLOPT_USERNAME, NULL);
1329 curl_easy_setopt(ch, CURLOPT_PASSWORD, NULL);
1330 #endif
1331
1332 #if PHP_HTTP_CURL_VERSION(7,21,3)
1333 if (curl->options.resolve) {
1334 curl_slist_free_all(curl->options.resolve);
1335 curl->options.resolve = NULL;
1336 }
1337 #endif
1338 curl->options.retry.count = 0;
1339 curl->options.retry.delay = 0;
1340 curl->options.redirects = 0;
1341 curl->options.encode_cookies = 1;
1342
1343 if (curl->options.headers) {
1344 curl_slist_free_all(curl->options.headers);
1345 curl->options.headers = NULL;
1346 }
1347
1348 php_http_buffer_reset(&curl->options.cookies);
1349 php_http_buffer_reset(&curl->options.ranges);
1350
1351 return SUCCESS;
1352 }
1353
1354 static php_http_client_curl_handler_t *php_http_client_curl_handler_init(php_http_client_t *h, php_resource_factory_t *rf)
1355 {
1356 void *handle;
1357 php_http_client_curl_handler_t *handler;
1358 TSRMLS_FETCH_FROM_CTX(h->ts);
1359
1360 if (!(handle = php_resource_factory_handle_ctor(rf, NULL TSRMLS_CC))) {
1361 php_http_error(HE_WARNING, PHP_HTTP_E_CLIENT, "Failed to initialize curl handle");
1362 return NULL;
1363 }
1364
1365 handler = ecalloc(1, sizeof(*handler));
1366 handler->rf = rf;
1367 handler->client = h;
1368 handler->handle = handle;
1369 handler->request.buffer = php_http_buffer_init(NULL);
1370 handler->request.parser = php_http_message_parser_init(NULL TSRMLS_CC);
1371 handler->request.message = php_http_message_init(NULL, 0, NULL TSRMLS_CC);
1372 handler->response.buffer = php_http_buffer_init(NULL);
1373 handler->response.parser = php_http_message_parser_init(NULL TSRMLS_CC);
1374 handler->response.message = php_http_message_init(NULL, 0, NULL TSRMLS_CC);
1375 php_http_buffer_init(&handler->options.cookies);
1376 php_http_buffer_init(&handler->options.ranges);
1377 zend_hash_init(&handler->options.cache, 0, NULL, ZVAL_PTR_DTOR, 0);
1378
1379 #if defined(ZTS)
1380 curl_easy_setopt(handle, CURLOPT_NOSIGNAL, 1L);
1381 #endif
1382 curl_easy_setopt(handle, CURLOPT_HEADER, 0L);
1383 curl_easy_setopt(handle, CURLOPT_FILETIME, 1L);
1384 curl_easy_setopt(handle, CURLOPT_AUTOREFERER, 1L);
1385 curl_easy_setopt(handle, CURLOPT_VERBOSE, 1L);
1386 curl_easy_setopt(handle, CURLOPT_NOPROGRESS, 0L);
1387 curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, NULL);
1388 curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, php_http_curle_dummy_callback);
1389 curl_easy_setopt(handle, CURLOPT_DEBUGFUNCTION, php_http_curle_raw_callback);
1390 curl_easy_setopt(handle, CURLOPT_READFUNCTION, php_http_curle_read_callback);
1391 curl_easy_setopt(handle, CURLOPT_IOCTLFUNCTION, php_http_curle_ioctl_callback);
1392 curl_easy_setopt(handle, CURLOPT_PROGRESSFUNCTION, php_http_curle_progress_callback);
1393 curl_easy_setopt(handle, CURLOPT_DEBUGDATA, handler);
1394 curl_easy_setopt(handle, CURLOPT_PROGRESSDATA, handler);
1395
1396 php_http_client_curl_handler_reset(handler);
1397
1398 return handler;
1399 }
1400
1401
1402 static STATUS php_http_client_curl_handler_prepare(php_http_client_curl_handler_t *curl, php_http_client_enqueue_t *enqueue)
1403 {
1404 size_t body_size;
1405 php_http_message_t *msg = enqueue->request;
1406 php_http_curle_storage_t *storage = php_http_curle_get_storage(curl->handle);
1407 TSRMLS_FETCH_FROM_CTX(curl->client->ts);
1408
1409 /* request url */
1410 if (!PHP_HTTP_INFO(msg).request.url) {
1411 php_http_error(HE_WARNING, PHP_HTTP_E_CLIENT, "Cannot request empty URL");
1412 return FAILURE;
1413 }
1414 storage->errorbuffer[0] = '\0';
1415 if (storage->url) {
1416 pefree(storage->url, 1);
1417 }
1418 storage->url = pestrdup(PHP_HTTP_INFO(msg).request.url, 1);
1419 curl_easy_setopt(curl->handle, CURLOPT_URL, storage->url);
1420
1421 /* request method */
1422 switch (php_http_select_str(PHP_HTTP_INFO(msg).request.method, 4, "GET", "HEAD", "POST", "PUT")) {
1423 case 0:
1424 curl_easy_setopt(curl->handle, CURLOPT_HTTPGET, 1L);
1425 break;
1426
1427 case 1:
1428 curl_easy_setopt(curl->handle, CURLOPT_NOBODY, 1L);
1429 break;
1430
1431 case 2:
1432 curl_easy_setopt(curl->handle, CURLOPT_POST, 1L);
1433 break;
1434
1435 case 3:
1436 curl_easy_setopt(curl->handle, CURLOPT_UPLOAD, 1L);
1437 break;
1438
1439 default: {
1440 if (PHP_HTTP_INFO(msg).request.method) {
1441 curl_easy_setopt(curl->handle, CURLOPT_CUSTOMREQUEST, PHP_HTTP_INFO(msg).request.method);
1442 } else {
1443 php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST_METHOD, "Cannot use empty request method");
1444 return FAILURE;
1445 }
1446 break;
1447 }
1448 }
1449
1450 /* request headers */
1451 php_http_message_update_headers(msg);
1452 if (zend_hash_num_elements(&msg->hdrs)) {
1453 php_http_array_hashkey_t header_key = php_http_array_hashkey_init(0);
1454 zval **header_val;
1455 HashPosition pos;
1456 php_http_buffer_t header;
1457
1458 php_http_buffer_init(&header);
1459 FOREACH_HASH_KEYVAL(pos, &msg->hdrs, header_key, header_val) {
1460 if (header_key.type == HASH_KEY_IS_STRING) {
1461 zval *header_cpy = php_http_ztyp(IS_STRING, *header_val);
1462
1463 php_http_buffer_appendf(&header, "%s: %s", header_key.str, Z_STRVAL_P(header_cpy));
1464 php_http_buffer_fix(&header);
1465 curl->options.headers = curl_slist_append(curl->options.headers, header.data);
1466 php_http_buffer_reset(&header);
1467
1468 zval_ptr_dtor(&header_cpy);
1469 }
1470 }
1471 php_http_buffer_dtor(&header);
1472 curl_easy_setopt(curl->handle, CURLOPT_HTTPHEADER, curl->options.headers);
1473 }
1474
1475 /* attach request body */
1476 if ((body_size = php_http_message_body_size(msg->body))) {
1477 /* RFC2616, section 4.3 (para. 4) states that »a message-body MUST NOT be included in a request if the
1478 * specification of the request method (section 5.1.1) does not allow sending an entity-body in request.«
1479 * Following the clause in section 5.1.1 (para. 2) that request methods »MUST be implemented with the
1480 * same semantics as those specified in section 9« reveal that not any single defined HTTP/1.1 method
1481 * does not allow a request body.
1482 */
1483 php_stream_rewind(php_http_message_body_stream(msg->body));
1484 curl_easy_setopt(curl->handle, CURLOPT_IOCTLDATA, msg->body);
1485 curl_easy_setopt(curl->handle, CURLOPT_READDATA, msg->body);
1486 curl_easy_setopt(curl->handle, CURLOPT_INFILESIZE, body_size);
1487 curl_easy_setopt(curl->handle, CURLOPT_POSTFIELDSIZE, body_size);
1488 }
1489
1490 php_http_options_apply(&php_http_curle_options, enqueue->options, curl);
1491
1492 return SUCCESS;
1493 }
1494
1495 static void php_http_client_curl_handler_dtor(php_http_client_curl_handler_t *handler)
1496 {
1497 TSRMLS_FETCH_FROM_CTX(handler->client->ts);
1498
1499 curl_easy_setopt(handler->handle, CURLOPT_NOPROGRESS, 1L);
1500 curl_easy_setopt(handler->handle, CURLOPT_PROGRESSFUNCTION, NULL);
1501 curl_easy_setopt(handler->handle, CURLOPT_VERBOSE, 0L);
1502 curl_easy_setopt(handler->handle, CURLOPT_DEBUGFUNCTION, NULL);
1503
1504 php_resource_factory_handle_dtor(handler->rf, handler->handle TSRMLS_CC);
1505 php_resource_factory_free(&handler->rf);
1506
1507 php_http_message_parser_free(&handler->request.parser);
1508 php_http_message_free(&handler->request.message);
1509 php_http_buffer_free(&handler->request.buffer);
1510 php_http_message_parser_free(&handler->response.parser);
1511 php_http_message_free(&handler->response.message);
1512 php_http_buffer_free(&handler->response.buffer);
1513 php_http_buffer_dtor(&handler->options.ranges);
1514 php_http_buffer_dtor(&handler->options.cookies);
1515 zend_hash_destroy(&handler->options.cache);
1516
1517 if (handler->options.headers) {
1518 curl_slist_free_all(handler->options.headers);
1519 handler->options.headers = NULL;
1520 }
1521
1522 efree(handler);
1523 }
1524
1525 static php_http_client_t *php_http_client_curl_init(php_http_client_t *h, void *handle)
1526 {
1527 php_http_client_curl_t *curl;
1528 TSRMLS_FETCH_FROM_CTX(h->ts);
1529
1530 if (!handle && !(handle = php_resource_factory_handle_ctor(h->rf, NULL TSRMLS_CC))) {
1531 php_http_error(HE_WARNING, PHP_HTTP_E_CLIENT_POOL, "Failed to initialize curl handle");
1532 return NULL;
1533 }
1534
1535 curl = ecalloc(1, sizeof(*curl));
1536 curl->handle = handle;
1537 curl->unfinished = 0;
1538 h->ctx = curl;
1539
1540 return h;
1541 }
1542
1543 static void php_http_client_curl_dtor(php_http_client_t *h)
1544 {
1545 php_http_client_curl_t *curl = h->ctx;
1546 TSRMLS_FETCH_FROM_CTX(h->ts);
1547
1548 #if PHP_HTTP_HAVE_EVENT
1549 if (curl->timeout) {
1550 efree(curl->timeout);
1551 curl->timeout = NULL;
1552 }
1553 #endif
1554 curl->unfinished = 0;
1555
1556 php_resource_factory_handle_dtor(h->rf, curl->handle TSRMLS_CC);
1557
1558 efree(curl);
1559 h->ctx = NULL;
1560 }
1561
1562 static void queue_dtor(php_http_client_enqueue_t *e)
1563 {
1564 php_http_client_curl_handler_t *handler = e->opaque;
1565
1566 if (handler->queue.dtor) {
1567 e->opaque = handler->queue.opaque;
1568 handler->queue.dtor(e);
1569 }
1570 php_http_client_curl_handler_dtor(handler);
1571 }
1572
1573 static php_resource_factory_t *create_rf(const char *url TSRMLS_DC)
1574 {
1575 php_url *purl;
1576 php_resource_factory_t *rf = NULL;
1577
1578 if ((purl = php_url_parse(url))) {
1579 char *id_str = NULL;
1580 size_t id_len = spprintf(&id_str, 0, "%s:%d", STR_PTR(purl->host), purl->port ? purl->port : 80);
1581 php_persistent_handle_factory_t *pf = php_persistent_handle_concede(NULL, ZEND_STRL("http\\Client\\Curl\\Request"), id_str, id_len, NULL, NULL TSRMLS_CC);
1582
1583 if (pf) {
1584 rf = php_resource_factory_init(NULL, php_persistent_handle_get_resource_factory_ops(), pf, (void (*)(void*)) php_persistent_handle_abandon);
1585 }
1586
1587 php_url_free(purl);
1588 efree(id_str);
1589 }
1590
1591 if (!rf) {
1592 rf = php_resource_factory_init(NULL, &php_http_curle_resource_factory_ops, NULL, NULL);
1593 }
1594
1595 return rf;
1596 }
1597
1598 static STATUS php_http_client_curl_enqueue(php_http_client_t *h, php_http_client_enqueue_t *enqueue)
1599 {
1600 CURLMcode rs;
1601 php_http_client_curl_t *curl = h->ctx;
1602 php_http_client_curl_handler_t *handler;
1603 php_http_client_progress_state_t *progress;
1604 TSRMLS_FETCH_FROM_CTX(h->ts);
1605
1606 handler = php_http_client_curl_handler_init(h, create_rf(enqueue->request->http.info.request.url TSRMLS_CC));
1607 if (!handler) {
1608 return FAILURE;
1609 }
1610
1611 if (SUCCESS != php_http_client_curl_handler_prepare(handler, enqueue)) {
1612 php_http_client_curl_handler_dtor(handler);
1613 return FAILURE;
1614 }
1615
1616 handler->queue = *enqueue;
1617 enqueue->opaque = handler;
1618 enqueue->dtor = queue_dtor;
1619
1620 if (CURLM_OK == (rs = curl_multi_add_handle(curl->handle, handler->handle))) {
1621 zend_llist_add_element(&h->requests, enqueue);
1622 ++curl->unfinished;
1623
1624 if (h->callback.progress.func && SUCCESS == php_http_client_getopt(h, PHP_HTTP_CLIENT_OPT_PROGRESS_INFO, enqueue->request, &progress)) {
1625 progress->info = "start";
1626 h->callback.progress.func(h->callback.progress.arg, h, &handler->queue, progress);
1627 progress->started = 1;
1628 }
1629
1630 return SUCCESS;
1631 } else {
1632 php_http_error(HE_WARNING, PHP_HTTP_E_CLIENT_POOL, "Could not enqueue request: %s", curl_multi_strerror(rs));
1633 return FAILURE;
1634 }
1635 }
1636
1637 static STATUS php_http_client_curl_dequeue(php_http_client_t *h, php_http_client_enqueue_t *enqueue)
1638 {
1639 CURLMcode rs;
1640 php_http_client_curl_t *curl = h->ctx;
1641 php_http_client_curl_handler_t *handler = enqueue->opaque;
1642 TSRMLS_FETCH_FROM_CTX(h->ts);
1643
1644 if (CURLM_OK == (rs = curl_multi_remove_handle(curl->handle, handler->handle))) {
1645 zend_llist_del_element(&h->requests, handler->handle, (int (*)(void *, void *)) compare_queue);
1646 return SUCCESS;
1647 } else {
1648 php_http_error(HE_WARNING, PHP_HTTP_E_CLIENT, "Could not dequeue request: %s", curl_multi_strerror(rs));
1649 }
1650
1651 return FAILURE;
1652 }
1653
1654 static void php_http_client_curl_reset(php_http_client_t *h)
1655 {
1656 zend_llist_element *next_el, *this_el;
1657
1658 for (this_el = h->requests.head; this_el; this_el = next_el) {
1659 next_el = this_el->next;
1660 php_http_client_curl_dequeue(h, (void *) this_el->data);
1661 }
1662 }
1663
1664 #ifdef PHP_WIN32
1665 # define SELECT_ERROR SOCKET_ERROR
1666 #else
1667 # define SELECT_ERROR -1
1668 #endif
1669
1670 static STATUS php_http_client_curl_wait(php_http_client_t *h, struct timeval *custom_timeout)
1671 {
1672 int MAX;
1673 fd_set R, W, E;
1674 struct timeval timeout;
1675 php_http_client_curl_t *curl = h->ctx;
1676
1677 #if PHP_HTTP_HAVE_EVENT
1678 if (curl->useevents) {
1679 TSRMLS_FETCH_FROM_CTX(h->ts);
1680
1681 php_http_error(HE_WARNING, PHP_HTTP_E_RUNTIME, "not implemented");
1682 return FAILURE;
1683 }
1684 #endif
1685
1686 FD_ZERO(&R);
1687 FD_ZERO(&W);
1688 FD_ZERO(&E);
1689
1690 if (CURLM_OK == curl_multi_fdset(curl->handle, &R, &W, &E, &MAX)) {
1691 if (custom_timeout && timerisset(custom_timeout)) {
1692 timeout = *custom_timeout;
1693 } else {
1694 long max_tout = 1000;
1695
1696 if ((CURLM_OK == curl_multi_timeout(curl->handle, &max_tout)) && (max_tout > 0)) {
1697 timeout.tv_sec = max_tout / 1000;
1698 timeout.tv_usec = (max_tout % 1000) * 1000;
1699 } else {
1700 timeout.tv_sec = 0;
1701 timeout.tv_usec = 1000;
1702 }
1703 }
1704
1705 if (MAX == -1) {
1706 php_http_sleep((double) timeout.tv_sec + (double) (timeout.tv_usec / PHP_HTTP_MCROSEC));
1707 return SUCCESS;
1708 } else if (SELECT_ERROR != select(MAX + 1, &R, &W, &E, &timeout)) {
1709 return SUCCESS;
1710 }
1711 }
1712 return FAILURE;
1713 }
1714
1715 static int php_http_client_curl_once(php_http_client_t *h)
1716 {
1717 php_http_client_curl_t *curl = h->ctx;
1718
1719 #if PHP_HTTP_HAVE_EVENT
1720 if (curl->useevents) {
1721 TSRMLS_FETCH_FROM_CTX(h->ts);
1722 php_http_error(HE_WARNING, PHP_HTTP_E_RUNTIME, "not implemented");
1723 return FAILURE;
1724 }
1725 #endif
1726
1727 while (CURLM_CALL_MULTI_PERFORM == curl_multi_perform(curl->handle, &curl->unfinished));
1728
1729 php_http_curlm_responsehandler(h);
1730
1731 return curl->unfinished;
1732
1733 }
1734
1735 static STATUS php_http_client_curl_exec(php_http_client_t *h)
1736 {
1737 TSRMLS_FETCH_FROM_CTX(h->ts);
1738
1739 #if PHP_HTTP_HAVE_EVENT
1740 php_http_client_curl_t *curl = h->ctx;
1741
1742 if (curl->useevents) {
1743 php_http_curlm_timeout_callback(CURL_SOCKET_TIMEOUT, /*EV_READ|EV_WRITE*/0, h);
1744 do {
1745 int ev_rc = event_base_dispatch(PHP_HTTP_G->curl.event_base);
1746
1747 #if DBG_EVENTS
1748 fprintf(stderr, "%c", "X.0"[ev_rc+1]);
1749 #endif
1750
1751 if (ev_rc < 0) {
1752 php_http_error(HE_ERROR, PHP_HTTP_E_RUNTIME, "Error in event_base_dispatch()");
1753 return FAILURE;
1754 }
1755 } while (curl->unfinished);
1756 } else
1757 #endif
1758 {
1759 while (php_http_client_curl_once(h)) {
1760 if (SUCCESS != php_http_client_curl_wait(h, NULL)) {
1761 #ifdef PHP_WIN32
1762 /* see http://msdn.microsoft.com/library/en-us/winsock/winsock/windows_sockets_error_codes_2.asp */
1763 php_http_error(HE_WARNING, PHP_HTTP_E_SOCKET, "WinSock error: %d", WSAGetLastError());
1764 #else
1765 php_http_error(HE_WARNING, PHP_HTTP_E_SOCKET, strerror(errno));
1766 #endif
1767 return FAILURE;
1768 }
1769 }
1770 }
1771
1772 return SUCCESS;
1773 }
1774
1775 static STATUS php_http_client_curl_setopt(php_http_client_t *h, php_http_client_setopt_opt_t opt, void *arg)
1776 {
1777 php_http_client_curl_t *curl = h->ctx;
1778
1779 switch (opt) {
1780 case PHP_HTTP_CLIENT_OPT_ENABLE_PIPELINING:
1781 if (CURLM_OK != curl_multi_setopt(curl->handle, CURLMOPT_PIPELINING, (long) *((zend_bool *) arg))) {
1782 return FAILURE;
1783 }
1784 break;
1785
1786 case PHP_HTTP_CLIENT_OPT_USE_EVENTS:
1787 #if PHP_HTTP_HAVE_EVENT
1788 if ((curl->useevents = *((zend_bool *) arg))) {
1789 if (!curl->timeout) {
1790 curl->timeout = ecalloc(1, sizeof(struct event));
1791 }
1792 curl_multi_setopt(curl->handle, CURLMOPT_SOCKETDATA, h);
1793 curl_multi_setopt(curl->handle, CURLMOPT_SOCKETFUNCTION, php_http_curlm_socket_callback);
1794 curl_multi_setopt(curl->handle, CURLMOPT_TIMERDATA, h);
1795 curl_multi_setopt(curl->handle, CURLMOPT_TIMERFUNCTION, php_http_curlm_timer_callback);
1796 } else {
1797 curl_multi_setopt(curl->handle, CURLMOPT_SOCKETDATA, NULL);
1798 curl_multi_setopt(curl->handle, CURLMOPT_SOCKETFUNCTION, NULL);
1799 curl_multi_setopt(curl->handle, CURLMOPT_TIMERDATA, NULL);
1800 curl_multi_setopt(curl->handle, CURLMOPT_TIMERFUNCTION, NULL);
1801 }
1802 break;
1803 #endif
1804
1805 default:
1806 return FAILURE;
1807 }
1808 return SUCCESS;
1809 }
1810
1811 static STATUS php_http_client_curl_getopt(php_http_client_t *h, php_http_client_getopt_opt_t opt, void *arg, void **res)
1812 {
1813 php_http_client_enqueue_t *enqueue;
1814
1815 switch (opt) {
1816 case PHP_HTTP_CLIENT_OPT_PROGRESS_INFO:
1817 if ((enqueue = php_http_client_enqueued(h, arg, NULL))) {
1818 php_http_client_curl_handler_t *handler = enqueue->opaque;
1819
1820 *((php_http_client_progress_state_t **) res) = &handler->progress;
1821 return SUCCESS;
1822 }
1823 break;
1824
1825 case PHP_HTTP_CLIENT_OPT_TRANSFER_INFO:
1826 if ((enqueue = php_http_client_enqueued(h, arg, NULL))) {
1827 php_http_client_curl_handler_t *handler = enqueue->opaque;
1828
1829 php_http_curle_get_info(handler->handle, *(HashTable **) res);
1830 return SUCCESS;
1831 }
1832 break;
1833
1834 default:
1835 break;
1836 }
1837
1838 return FAILURE;
1839 }
1840
1841 static php_http_client_ops_t php_http_client_curl_ops = {
1842 &php_http_curlm_resource_factory_ops,
1843 php_http_client_curl_init,
1844 NULL /* copy */,
1845 php_http_client_curl_dtor,
1846 php_http_client_curl_reset,
1847 php_http_client_curl_exec,
1848 php_http_client_curl_wait,
1849 php_http_client_curl_once,
1850 php_http_client_curl_enqueue,
1851 php_http_client_curl_dequeue,
1852 php_http_client_curl_setopt,
1853 php_http_client_curl_getopt
1854 };
1855
1856 PHP_HTTP_API php_http_client_ops_t *php_http_client_curl_get_ops(void)
1857 {
1858 return &php_http_client_curl_ops;
1859 }
1860
1861 PHP_MINIT_FUNCTION(http_client_curl)
1862 {
1863 php_http_options_t *options;
1864 php_http_client_driver_t driver = {
1865 &php_http_client_curl_ops
1866 };
1867
1868 if (SUCCESS != php_http_client_driver_add(ZEND_STRL("curl"), &driver)) {
1869 return FAILURE;
1870 }
1871
1872 if (SUCCESS != php_persistent_handle_provide(ZEND_STRL("http\\Client\\Curl"), &php_http_curlm_resource_factory_ops, NULL, NULL TSRMLS_CC)) {
1873 return FAILURE;
1874 }
1875 if (SUCCESS != php_persistent_handle_provide(ZEND_STRL("http\\Client\\Curl\\Request"), &php_http_curle_resource_factory_ops, NULL, NULL TSRMLS_CC)) {
1876 return FAILURE;
1877 }
1878
1879 if ((options = php_http_options_init(&php_http_curle_options, 1))) {
1880 options->getter = php_http_curle_get_option;
1881 options->setter = php_http_curle_set_option;
1882
1883 php_http_curle_options_init(options TSRMLS_CC);
1884 }
1885
1886 /*
1887 * HTTP Protocol Version Constants
1888 */
1889 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "HTTP_VERSION_1_0", CURL_HTTP_VERSION_1_0, CONST_CS|CONST_PERSISTENT);
1890 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "HTTP_VERSION_1_1", CURL_HTTP_VERSION_1_1, CONST_CS|CONST_PERSISTENT);
1891 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "HTTP_VERSION_ANY", CURL_HTTP_VERSION_NONE, CONST_CS|CONST_PERSISTENT);
1892
1893 /*
1894 * SSL Version Constants
1895 */
1896 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "SSL_VERSION_TLSv1", CURL_SSLVERSION_TLSv1, CONST_CS|CONST_PERSISTENT);
1897 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "SSL_VERSION_SSLv2", CURL_SSLVERSION_SSLv2, CONST_CS|CONST_PERSISTENT);
1898 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "SSL_VERSION_SSLv3", CURL_SSLVERSION_SSLv3, CONST_CS|CONST_PERSISTENT);
1899 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "SSL_VERSION_ANY", CURL_SSLVERSION_DEFAULT, CONST_CS|CONST_PERSISTENT);
1900
1901 /*
1902 * DNS IPvX resolving
1903 */
1904 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "IPRESOLVE_V4", CURL_IPRESOLVE_V4, CONST_CS|CONST_PERSISTENT);
1905 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "IPRESOLVE_V6", CURL_IPRESOLVE_V6, CONST_CS|CONST_PERSISTENT);
1906 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "IPRESOLVE_ANY", CURL_IPRESOLVE_WHATEVER, CONST_CS|CONST_PERSISTENT);
1907
1908 /*
1909 * Auth Constants
1910 */
1911 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "AUTH_BASIC", CURLAUTH_BASIC, CONST_CS|CONST_PERSISTENT);
1912 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "AUTH_DIGEST", CURLAUTH_DIGEST, CONST_CS|CONST_PERSISTENT);
1913 #if PHP_HTTP_CURL_VERSION(7,19,3)
1914 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "AUTH_DIGEST_IE", CURLAUTH_DIGEST_IE, CONST_CS|CONST_PERSISTENT);
1915 #endif
1916 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "AUTH_NTLM", CURLAUTH_NTLM, CONST_CS|CONST_PERSISTENT);
1917 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "AUTH_GSSNEG", CURLAUTH_GSSNEGOTIATE, CONST_CS|CONST_PERSISTENT);
1918 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "AUTH_ANY", CURLAUTH_ANY, CONST_CS|CONST_PERSISTENT);
1919
1920 /*
1921 * Proxy Type Constants
1922 */
1923 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "PROXY_SOCKS4", CURLPROXY_SOCKS4, CONST_CS|CONST_PERSISTENT);
1924 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "PROXY_SOCKS4A", CURLPROXY_SOCKS5, CONST_CS|CONST_PERSISTENT);
1925 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "PROXY_SOCKS5_HOSTNAME", CURLPROXY_SOCKS5, CONST_CS|CONST_PERSISTENT);
1926 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "PROXY_SOCKS5", CURLPROXY_SOCKS5, CONST_CS|CONST_PERSISTENT);
1927 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "PROXY_HTTP", CURLPROXY_HTTP, CONST_CS|CONST_PERSISTENT);
1928 # if PHP_HTTP_CURL_VERSION(7,19,4)
1929 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "PROXY_HTTP_1_0", CURLPROXY_HTTP_1_0, CONST_CS|CONST_PERSISTENT);
1930 # endif
1931
1932 /*
1933 * Post Redirection Constants
1934 */
1935 #if PHP_HTTP_CURL_VERSION(7,19,1)
1936 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "POSTREDIR_301", CURL_REDIR_POST_301, CONST_CS|CONST_PERSISTENT);
1937 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "POSTREDIR_302", CURL_REDIR_POST_302, CONST_CS|CONST_PERSISTENT);
1938 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "POSTREDIR_ALL", CURL_REDIR_POST_ALL, CONST_CS|CONST_PERSISTENT);
1939 #endif
1940
1941 return SUCCESS;
1942 }
1943
1944 PHP_MSHUTDOWN_FUNCTION(http_client_curl)
1945 {
1946 php_http_options_dtor(&php_http_curle_options);
1947 return SUCCESS;
1948 }
1949
1950 #if PHP_HTTP_HAVE_EVENT
1951 PHP_RINIT_FUNCTION(http_client_curl)
1952 {
1953 if (!PHP_HTTP_G->curl.event_base && !(PHP_HTTP_G->curl.event_base = event_base_new())) {
1954 return FAILURE;
1955 }
1956 return SUCCESS;
1957 }
1958 PHP_RSHUTDOWN_FUNCTION(http_client_curl)
1959 {
1960 if (PHP_HTTP_G->curl.event_base) {
1961 event_base_free(PHP_HTTP_G->curl.event_base);
1962 PHP_HTTP_G->curl.event_base = NULL;
1963 }
1964 return SUCCESS;
1965 }
1966 #endif /* PHP_HTTP_HAVE_EVENT */
1967
1968 #endif /* PHP_HTTP_HAVE_CURL */
1969
1970 /*
1971 * Local variables:
1972 * tab-width: 4
1973 * c-basic-offset: 4
1974 * End:
1975 * vim600: noet sw=4 ts=4 fdm=marker
1976 * vim<600: noet sw=4 ts=4
1977 */