a5af70dc426a96eca1aefbecf1ed5b87d7a1eb36
[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-2013, 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 if ((opt = php_http_option_register(registry, ZEND_STRL("proxyauthtype"), CURLOPT_PROXYAUTH, IS_LONG))) {
990 Z_LVAL(opt->defval) = CURLAUTH_ANYSAFE;
991 }
992 php_http_option_register(registry, ZEND_STRL("proxytunnel"), CURLOPT_HTTPPROXYTUNNEL, IS_BOOL);
993 #if PHP_HTTP_CURL_VERSION(7,19,4)
994 php_http_option_register(registry, ZEND_STRL("noproxy"), CURLOPT_NOPROXY, IS_STRING);
995 #endif
996
997 /* dns */
998 if ((opt = php_http_option_register(registry, ZEND_STRL("dns_cache_timeout"), CURLOPT_DNS_CACHE_TIMEOUT, IS_LONG))) {
999 Z_LVAL(opt->defval) = 60;
1000 }
1001 php_http_option_register(registry, ZEND_STRL("ipresolve"), CURLOPT_IPRESOLVE, IS_LONG);
1002 #if PHP_HTTP_CURL_VERSION(7,21,3)
1003 if ((opt = php_http_option_register(registry, ZEND_STRL("resolve"), CURLOPT_RESOLVE, IS_ARRAY))) {
1004 opt->setter = php_http_curle_option_set_resolve;
1005 }
1006 #endif
1007 #if PHP_HTTP_CURL_VERSION(7,24,0)
1008 if ((opt = php_http_option_register(registry, ZEND_STRL("dns_servers"), CURLOPT_DNS_SERVERS, IS_STRING))) {
1009 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN;
1010 }
1011 #endif
1012
1013 /* limits */
1014 php_http_option_register(registry, ZEND_STRL("low_speed_limit"), CURLOPT_LOW_SPEED_LIMIT, IS_LONG);
1015 php_http_option_register(registry, ZEND_STRL("low_speed_time"), CURLOPT_LOW_SPEED_TIME, IS_LONG);
1016
1017 /* LSF weirdance
1018 php_http_option_register(registry, ZEND_STRL("max_send_speed"), CURLOPT_MAX_SEND_SPEED_LARGE, IS_LONG);
1019 php_http_option_register(registry, ZEND_STRL("max_recv_speed"), CURLOPT_MAX_RECV_SPEED_LARGE, IS_LONG);
1020 */
1021
1022 /* connection handling */
1023 /* crashes
1024 if ((opt = php_http_option_register(registry, ZEND_STRL("maxconnects"), CURLOPT_MAXCONNECTS, IS_LONG))) {
1025 Z_LVAL(opt->defval) = 5;
1026 }
1027 */
1028 php_http_option_register(registry, ZEND_STRL("fresh_connect"), CURLOPT_FRESH_CONNECT, IS_BOOL);
1029 php_http_option_register(registry, ZEND_STRL("forbid_reuse"), CURLOPT_FORBID_REUSE, IS_BOOL);
1030
1031 /* outgoing interface */
1032 php_http_option_register(registry, ZEND_STRL("interface"), CURLOPT_INTERFACE, IS_STRING);
1033 if ((opt = php_http_option_register(registry, ZEND_STRL("portrange"), CURLOPT_LOCALPORT, IS_ARRAY))) {
1034 opt->setter = php_http_curle_option_set_portrange;
1035 }
1036
1037 /* another endpoint port */
1038 php_http_option_register(registry, ZEND_STRL("port"), CURLOPT_PORT, IS_LONG);
1039
1040 /* RFC4007 zone_id */
1041 #if PHP_HTTP_CURL_VERSION(7,19,0)
1042 php_http_option_register(registry, ZEND_STRL("address_scope"), CURLOPT_ADDRESS_SCOPE, IS_LONG);
1043 #endif
1044
1045 /* auth */
1046 if ((opt = php_http_option_register(registry, ZEND_STRL("httpauth"), CURLOPT_USERPWD, IS_STRING))) {
1047 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN;
1048 }
1049 if ((opt = php_http_option_register(registry, ZEND_STRL("httpauthtype"), CURLOPT_HTTPAUTH, IS_LONG))) {
1050 Z_LVAL(opt->defval) = CURLAUTH_ANYSAFE;
1051 }
1052
1053 /* redirects */
1054 if ((opt = php_http_option_register(registry, ZEND_STRL("redirect"), CURLOPT_FOLLOWLOCATION, IS_LONG))) {
1055 opt->setter = php_http_curle_option_set_redirect;
1056 }
1057 php_http_option_register(registry, ZEND_STRL("unrestrictedauth"), CURLOPT_UNRESTRICTED_AUTH, IS_BOOL);
1058 #if PHP_HTTP_CURL_VERSION(7,19,1)
1059 php_http_option_register(registry, ZEND_STRL("postredir"), CURLOPT_POSTREDIR, IS_BOOL);
1060 #else
1061 php_http_option_register(registry, ZEND_STRL("postredir"), CURLOPT_POST301, IS_BOOL);
1062 #endif
1063
1064 /* retries */
1065 if ((opt = php_http_option_register(registry, ZEND_STRL("retrycount"), 0, IS_LONG))) {
1066 opt->setter = php_http_curle_option_set_retrycount;
1067 }
1068 if ((opt = php_http_option_register(registry, ZEND_STRL("retrydelay"), 0, IS_DOUBLE))) {
1069 opt->setter = php_http_curle_option_set_retrydelay;
1070 }
1071
1072 /* referer */
1073 if ((opt = php_http_option_register(registry, ZEND_STRL("referer"), CURLOPT_REFERER, IS_STRING))) {
1074 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN;
1075 }
1076 if ((opt = php_http_option_register(registry, ZEND_STRL("autoreferer"), CURLOPT_AUTOREFERER, IS_BOOL))) {
1077 ZVAL_BOOL(&opt->defval, 1);
1078 }
1079
1080 /* useragent */
1081 if ((opt = php_http_option_register(registry, ZEND_STRL("useragent"), CURLOPT_USERAGENT, IS_STRING))) {
1082 /* don't check strlen, to allow sending no useragent at all */
1083 ZVAL_STRING(&opt->defval, "PECL::HTTP/" PHP_HTTP_EXT_VERSION " (PHP/" PHP_VERSION ")", 0);
1084 }
1085
1086 /* resume */
1087 if ((opt = php_http_option_register(registry, ZEND_STRL("resume"), CURLOPT_RESUME_FROM, IS_LONG))) {
1088 opt->setter = php_http_curle_option_set_resume;
1089 }
1090 /* ranges */
1091 if ((opt = php_http_option_register(registry, ZEND_STRL("range"), CURLOPT_RANGE, IS_ARRAY))) {
1092 opt->setter = php_http_curle_option_set_range;
1093 }
1094
1095 /* etag */
1096 if ((opt = php_http_option_register(registry, ZEND_STRL("etag"), 0, IS_STRING))) {
1097 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN;
1098 opt->setter = php_http_curle_option_set_etag;
1099 }
1100
1101 /* compression */
1102 if ((opt = php_http_option_register(registry, ZEND_STRL("compress"), 0, IS_BOOL))) {
1103 opt->setter = php_http_curle_option_set_compress;
1104 }
1105
1106 /* lastmodified */
1107 if ((opt = php_http_option_register(registry, ZEND_STRL("lastmodified"), 0, IS_LONG))) {
1108 opt->setter = php_http_curle_option_set_lastmodified;
1109 }
1110
1111 /* cookies */
1112 if ((opt = php_http_option_register(registry, ZEND_STRL("encodecookies"), 0, IS_BOOL))) {
1113 opt->setter = php_http_curle_option_set_encodecookies;
1114 ZVAL_BOOL(&opt->defval, 1);
1115 }
1116 if ((opt = php_http_option_register(registry, ZEND_STRL("cookies"), 0, IS_ARRAY))) {
1117 opt->setter = php_http_curle_option_set_cookies;
1118 }
1119
1120 /* cookiesession, don't load session cookies from cookiestore */
1121 php_http_option_register(registry, ZEND_STRL("cookiesession"), CURLOPT_COOKIESESSION, IS_BOOL);
1122 /* cookiestore, read initial cookies from that file and store cookies back into that file */
1123 if ((opt = php_http_option_register(registry, ZEND_STRL("cookiestore"), 0, IS_STRING))) {
1124 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN;
1125 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_BASEDIR;
1126 opt->setter = php_http_curle_option_set_cookiestore;
1127 }
1128
1129 /* maxfilesize */
1130 php_http_option_register(registry, ZEND_STRL("maxfilesize"), CURLOPT_MAXFILESIZE, IS_LONG);
1131
1132 /* http protocol version */
1133 php_http_option_register(registry, ZEND_STRL("protocol"), CURLOPT_HTTP_VERSION, IS_LONG);
1134
1135 /* timeouts */
1136 if ((opt = php_http_option_register(registry, ZEND_STRL("timeout"), CURLOPT_TIMEOUT_MS, IS_DOUBLE))) {
1137 opt->flags |= PHP_HTTP_CURLE_OPTION_TRANSFORM_MS;
1138 }
1139 if ((opt = php_http_option_register(registry, ZEND_STRL("connecttimeout"), CURLOPT_CONNECTTIMEOUT_MS, IS_DOUBLE))) {
1140 opt->flags |= PHP_HTTP_CURLE_OPTION_TRANSFORM_MS;
1141 Z_DVAL(opt->defval) = 3;
1142 }
1143
1144 /* tcp */
1145 #if PHP_HTTP_CURL_VERSION(7,25,0)
1146 php_http_option_register(registry, ZEND_STRL("tcp_keepalive"), CURLOPT_TCP_KEEPALIVE, IS_BOOL);
1147 if ((opt = php_http_option_register(registry, ZEND_STRL("tcp_keepidle"), CURLOPT_TCP_KEEPIDLE, IS_LONG))) {
1148 Z_LVAL(opt->defval) = 60;
1149 }
1150 if ((opt = php_http_option_register(registry, ZEND_STRL("tcp_keepintvl"), CURLOPT_TCP_KEEPINTVL, IS_LONG))) {
1151 Z_LVAL(opt->defval) = 60;
1152 }
1153 #endif
1154
1155 /* ssl */
1156 if ((opt = php_http_option_register(registry, ZEND_STRL("ssl"), 0, IS_ARRAY))) {
1157 registry = &opt->suboptions;
1158
1159 if ((opt = php_http_option_register(registry, ZEND_STRL("cert"), CURLOPT_SSLCERT, IS_STRING))) {
1160 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN;
1161 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_BASEDIR;
1162 }
1163 php_http_option_register(registry, ZEND_STRL("certtype"), CURLOPT_SSLCERTTYPE, IS_STRING);
1164 php_http_option_register(registry, ZEND_STRL("certpasswd"), CURLOPT_SSLCERTPASSWD, IS_STRING);
1165
1166 if ((opt = php_http_option_register(registry, ZEND_STRL("key"), CURLOPT_SSLKEY, IS_STRING))) {
1167 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN;
1168 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_BASEDIR;
1169 }
1170 php_http_option_register(registry, ZEND_STRL("keytype"), CURLOPT_SSLKEYTYPE, IS_STRING);
1171 php_http_option_register(registry, ZEND_STRL("keypasswd"), CURLOPT_SSLKEYPASSWD, IS_STRING);
1172 php_http_option_register(registry, ZEND_STRL("engine"), CURLOPT_SSLENGINE, IS_STRING);
1173 php_http_option_register(registry, ZEND_STRL("version"), CURLOPT_SSLVERSION, IS_LONG);
1174 if ((opt = php_http_option_register(registry, ZEND_STRL("verifypeer"), CURLOPT_SSL_VERIFYPEER, IS_BOOL))) {
1175 ZVAL_BOOL(&opt->defval, 1);
1176 }
1177 if ((opt = php_http_option_register(registry, ZEND_STRL("verifyhost"), CURLOPT_SSL_VERIFYHOST, IS_BOOL))) {
1178 ZVAL_BOOL(&opt->defval, 1);
1179 opt->setter = php_http_curle_option_set_ssl_verifyhost;
1180 }
1181 php_http_option_register(registry, ZEND_STRL("cipher_list"), CURLOPT_SSL_CIPHER_LIST, IS_STRING);
1182 if ((opt = php_http_option_register(registry, ZEND_STRL("cainfo"), CURLOPT_CAINFO, IS_STRING))) {
1183 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN;
1184 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_BASEDIR;
1185 #ifdef PHP_HTTP_CURL_CAINFO
1186 ZVAL_STRING(&opt->defval, PHP_HTTP_CURL_CAINFO, 0);
1187 #endif
1188 }
1189 if ((opt = php_http_option_register(registry, ZEND_STRL("capath"), CURLOPT_CAPATH, 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("random_file"), CURLOPT_RANDOM_FILE, IS_STRING))) {
1194 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN;
1195 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_BASEDIR;
1196 }
1197 if ((opt = php_http_option_register(registry, ZEND_STRL("egdsocket"), CURLOPT_EGDSOCKET, IS_STRING))) {
1198 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN;
1199 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_BASEDIR;
1200 }
1201 #if PHP_HTTP_CURL_VERSION(7,19,0)
1202 if ((opt = php_http_option_register(registry, ZEND_STRL("issuercert"), CURLOPT_ISSUERCERT, IS_STRING))) {
1203 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN;
1204 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_BASEDIR;
1205 }
1206 # ifdef PHP_HTTP_HAVE_OPENSSL
1207 if ((opt = php_http_option_register(registry, ZEND_STRL("crlfile"), CURLOPT_CRLFILE, IS_STRING))) {
1208 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN;
1209 opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_BASEDIR;
1210 }
1211 # endif
1212 #endif
1213 #if PHP_HTTP_CURL_VERSION(7,19,1) && defined(PHP_HTTP_HAVE_OPENSSL)
1214 php_http_option_register(registry, ZEND_STRL("certinfo"), CURLOPT_CERTINFO, IS_BOOL);
1215 #endif
1216 }
1217 }
1218
1219 static zval *php_http_curle_get_option(php_http_option_t *opt, HashTable *options, void *userdata)
1220 {
1221 php_http_client_curl_handler_t *curl = userdata;
1222 zval *option;
1223
1224 if ((option = php_http_option_get(opt, options, NULL))) {
1225 option = php_http_ztyp(opt->type, option);
1226 zend_hash_quick_update(&curl->options.cache, opt->name.s, opt->name.l, opt->name.h, &option, sizeof(zval *), NULL);
1227 }
1228 return option;
1229 }
1230
1231 static STATUS php_http_curle_set_option(php_http_option_t *opt, zval *val, void *userdata)
1232 {
1233 php_http_client_curl_handler_t *curl = userdata;
1234 CURL *ch = curl->handle;
1235 zval tmp;
1236 STATUS rv = SUCCESS;
1237 TSRMLS_FETCH_FROM_CTX(curl->client->ts);
1238
1239 if (!val) {
1240 val = &opt->defval;
1241 }
1242
1243 switch (opt->type) {
1244 case IS_BOOL:
1245 if (opt->setter) {
1246 rv = opt->setter(opt, val, curl);
1247 } else if (CURLE_OK != curl_easy_setopt(ch, opt->option, (long) Z_BVAL_P(val))) {
1248 rv = FAILURE;
1249 }
1250 break;
1251
1252 case IS_LONG:
1253 if (opt->setter) {
1254 rv = opt->setter(opt, val, curl);
1255 } else if (CURLE_OK != curl_easy_setopt(ch, opt->option, Z_LVAL_P(val))) {
1256 rv = FAILURE;
1257 }
1258 break;
1259
1260 case IS_STRING:
1261 if (!(opt->flags & PHP_HTTP_CURLE_OPTION_CHECK_STRLEN) || Z_STRLEN_P(val)) {
1262 if (!(opt->flags & PHP_HTTP_CURLE_OPTION_CHECK_BASEDIR) || !Z_STRVAL_P(val) || SUCCESS == php_check_open_basedir(Z_STRVAL_P(val) TSRMLS_CC)) {
1263 if (opt->setter) {
1264 rv = opt->setter(opt, val, curl);
1265 } else if (CURLE_OK != curl_easy_setopt(ch, opt->option, Z_STRVAL_P(val))) {
1266 rv = FAILURE;
1267 }
1268 }
1269 }
1270 break;
1271
1272 case IS_DOUBLE:
1273 if (opt->flags & PHP_HTTP_CURLE_OPTION_TRANSFORM_MS) {
1274 tmp = *val;
1275 Z_DVAL(tmp) *= 1000;
1276 val = &tmp;
1277 }
1278 if (opt->setter) {
1279 rv = opt->setter(opt, val, curl);
1280 } else if (CURLE_OK != curl_easy_setopt(ch, opt->option, (long) Z_DVAL_P(val))) {
1281 rv = FAILURE;
1282 }
1283 break;
1284
1285 case IS_ARRAY:
1286 if (opt->setter) {
1287 rv = opt->setter(opt, val, curl);
1288 } else if (Z_TYPE_P(val) != IS_NULL) {
1289 rv = php_http_options_apply(&opt->suboptions, Z_ARRVAL_P(val), curl);
1290 }
1291 break;
1292
1293 default:
1294 if (opt->setter) {
1295 rv = opt->setter(opt, val, curl);
1296 } else {
1297 rv = FAILURE;
1298 }
1299 break;
1300 }
1301 if (rv != SUCCESS) {
1302 php_http_error(HE_NOTICE, PHP_HTTP_E_CLIENT, "Could not set option %s", opt->name.s);
1303 }
1304 return rv;
1305 }
1306
1307
1308 /* client ops */
1309
1310 static STATUS php_http_client_curl_handler_reset(php_http_client_curl_handler_t *curl)
1311 {
1312 CURL *ch = curl->handle;
1313 php_http_curle_storage_t *st;
1314
1315 if ((st = php_http_curle_get_storage(ch))) {
1316 if (st->url) {
1317 pefree(st->url, 1);
1318 st->url = NULL;
1319 }
1320 if (st->cookiestore) {
1321 pefree(st->cookiestore, 1);
1322 st->cookiestore = NULL;
1323 }
1324 st->errorbuffer[0] = '\0';
1325 }
1326
1327 curl_easy_setopt(ch, CURLOPT_URL, NULL);
1328 /* libcurl < 7.19.6 does not clear auth info with USERPWD set to NULL */
1329 #if PHP_HTTP_CURL_VERSION(7,19,1)
1330 curl_easy_setopt(ch, CURLOPT_PROXYUSERNAME, NULL);
1331 curl_easy_setopt(ch, CURLOPT_PROXYPASSWORD, NULL);
1332 curl_easy_setopt(ch, CURLOPT_USERNAME, NULL);
1333 curl_easy_setopt(ch, CURLOPT_PASSWORD, NULL);
1334 #endif
1335
1336 #if PHP_HTTP_CURL_VERSION(7,21,3)
1337 if (curl->options.resolve) {
1338 curl_slist_free_all(curl->options.resolve);
1339 curl->options.resolve = NULL;
1340 }
1341 #endif
1342 curl->options.retry.count = 0;
1343 curl->options.retry.delay = 0;
1344 curl->options.redirects = 0;
1345 curl->options.encode_cookies = 1;
1346
1347 if (curl->options.headers) {
1348 curl_slist_free_all(curl->options.headers);
1349 curl->options.headers = NULL;
1350 }
1351
1352 php_http_buffer_reset(&curl->options.cookies);
1353 php_http_buffer_reset(&curl->options.ranges);
1354
1355 return SUCCESS;
1356 }
1357
1358 static php_http_client_curl_handler_t *php_http_client_curl_handler_init(php_http_client_t *h, php_resource_factory_t *rf)
1359 {
1360 void *handle;
1361 php_http_client_curl_handler_t *handler;
1362 TSRMLS_FETCH_FROM_CTX(h->ts);
1363
1364 if (!(handle = php_resource_factory_handle_ctor(rf, NULL TSRMLS_CC))) {
1365 php_http_error(HE_WARNING, PHP_HTTP_E_CLIENT, "Failed to initialize curl handle");
1366 return NULL;
1367 }
1368
1369 handler = ecalloc(1, sizeof(*handler));
1370 handler->rf = rf;
1371 handler->client = h;
1372 handler->handle = handle;
1373 handler->request.buffer = php_http_buffer_init(NULL);
1374 handler->request.parser = php_http_message_parser_init(NULL TSRMLS_CC);
1375 handler->request.message = php_http_message_init(NULL, 0, NULL TSRMLS_CC);
1376 handler->response.buffer = php_http_buffer_init(NULL);
1377 handler->response.parser = php_http_message_parser_init(NULL TSRMLS_CC);
1378 handler->response.message = php_http_message_init(NULL, 0, NULL TSRMLS_CC);
1379 php_http_buffer_init(&handler->options.cookies);
1380 php_http_buffer_init(&handler->options.ranges);
1381 zend_hash_init(&handler->options.cache, 0, NULL, ZVAL_PTR_DTOR, 0);
1382
1383 #if defined(ZTS)
1384 curl_easy_setopt(handle, CURLOPT_NOSIGNAL, 1L);
1385 #endif
1386 curl_easy_setopt(handle, CURLOPT_HEADER, 0L);
1387 curl_easy_setopt(handle, CURLOPT_FILETIME, 1L);
1388 curl_easy_setopt(handle, CURLOPT_AUTOREFERER, 1L);
1389 curl_easy_setopt(handle, CURLOPT_VERBOSE, 1L);
1390 curl_easy_setopt(handle, CURLOPT_NOPROGRESS, 0L);
1391 curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, NULL);
1392 curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, php_http_curle_dummy_callback);
1393 curl_easy_setopt(handle, CURLOPT_DEBUGFUNCTION, php_http_curle_raw_callback);
1394 curl_easy_setopt(handle, CURLOPT_READFUNCTION, php_http_curle_read_callback);
1395 curl_easy_setopt(handle, CURLOPT_IOCTLFUNCTION, php_http_curle_ioctl_callback);
1396 curl_easy_setopt(handle, CURLOPT_PROGRESSFUNCTION, php_http_curle_progress_callback);
1397 curl_easy_setopt(handle, CURLOPT_DEBUGDATA, handler);
1398 curl_easy_setopt(handle, CURLOPT_PROGRESSDATA, handler);
1399
1400 php_http_client_curl_handler_reset(handler);
1401
1402 return handler;
1403 }
1404
1405
1406 static STATUS php_http_client_curl_handler_prepare(php_http_client_curl_handler_t *curl, php_http_client_enqueue_t *enqueue)
1407 {
1408 size_t body_size;
1409 php_http_message_t *msg = enqueue->request;
1410 php_http_curle_storage_t *storage = php_http_curle_get_storage(curl->handle);
1411 TSRMLS_FETCH_FROM_CTX(curl->client->ts);
1412
1413 /* request url */
1414 if (!PHP_HTTP_INFO(msg).request.url) {
1415 php_http_error(HE_WARNING, PHP_HTTP_E_CLIENT, "Cannot request empty URL");
1416 return FAILURE;
1417 }
1418 storage->errorbuffer[0] = '\0';
1419 if (storage->url) {
1420 pefree(storage->url, 1);
1421 }
1422 storage->url = pestrdup(PHP_HTTP_INFO(msg).request.url, 1);
1423 curl_easy_setopt(curl->handle, CURLOPT_URL, storage->url);
1424
1425 /* request method */
1426 switch (php_http_select_str(PHP_HTTP_INFO(msg).request.method, 4, "GET", "HEAD", "POST", "PUT")) {
1427 case 0:
1428 curl_easy_setopt(curl->handle, CURLOPT_HTTPGET, 1L);
1429 break;
1430
1431 case 1:
1432 curl_easy_setopt(curl->handle, CURLOPT_NOBODY, 1L);
1433 break;
1434
1435 case 2:
1436 curl_easy_setopt(curl->handle, CURLOPT_POST, 1L);
1437 break;
1438
1439 case 3:
1440 curl_easy_setopt(curl->handle, CURLOPT_UPLOAD, 1L);
1441 break;
1442
1443 default: {
1444 if (PHP_HTTP_INFO(msg).request.method) {
1445 curl_easy_setopt(curl->handle, CURLOPT_CUSTOMREQUEST, PHP_HTTP_INFO(msg).request.method);
1446 } else {
1447 php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST_METHOD, "Cannot use empty request method");
1448 return FAILURE;
1449 }
1450 break;
1451 }
1452 }
1453
1454 /* request headers */
1455 php_http_message_update_headers(msg);
1456 if (zend_hash_num_elements(&msg->hdrs)) {
1457 php_http_array_hashkey_t header_key = php_http_array_hashkey_init(0);
1458 zval **header_val;
1459 HashPosition pos;
1460 php_http_buffer_t header;
1461
1462 php_http_buffer_init(&header);
1463 FOREACH_HASH_KEYVAL(pos, &msg->hdrs, header_key, header_val) {
1464 if (header_key.type == HASH_KEY_IS_STRING) {
1465 zval *header_cpy = php_http_ztyp(IS_STRING, *header_val);
1466
1467 php_http_buffer_appendf(&header, "%s: %s", header_key.str, Z_STRVAL_P(header_cpy));
1468 php_http_buffer_fix(&header);
1469 curl->options.headers = curl_slist_append(curl->options.headers, header.data);
1470 php_http_buffer_reset(&header);
1471
1472 zval_ptr_dtor(&header_cpy);
1473 }
1474 }
1475 php_http_buffer_dtor(&header);
1476 curl_easy_setopt(curl->handle, CURLOPT_HTTPHEADER, curl->options.headers);
1477 }
1478
1479 /* attach request body */
1480 if ((body_size = php_http_message_body_size(msg->body))) {
1481 /* RFC2616, section 4.3 (para. 4) states that »a message-body MUST NOT be included in a request if the
1482 * specification of the request method (section 5.1.1) does not allow sending an entity-body in request.«
1483 * Following the clause in section 5.1.1 (para. 2) that request methods »MUST be implemented with the
1484 * same semantics as those specified in section 9« reveal that not any single defined HTTP/1.1 method
1485 * does not allow a request body.
1486 */
1487 php_stream_rewind(php_http_message_body_stream(msg->body));
1488 curl_easy_setopt(curl->handle, CURLOPT_IOCTLDATA, msg->body);
1489 curl_easy_setopt(curl->handle, CURLOPT_READDATA, msg->body);
1490 curl_easy_setopt(curl->handle, CURLOPT_INFILESIZE, body_size);
1491 curl_easy_setopt(curl->handle, CURLOPT_POSTFIELDSIZE, body_size);
1492 }
1493
1494 php_http_options_apply(&php_http_curle_options, enqueue->options, curl);
1495
1496 return SUCCESS;
1497 }
1498
1499 static void php_http_client_curl_handler_dtor(php_http_client_curl_handler_t *handler)
1500 {
1501 TSRMLS_FETCH_FROM_CTX(handler->client->ts);
1502
1503 curl_easy_setopt(handler->handle, CURLOPT_NOPROGRESS, 1L);
1504 curl_easy_setopt(handler->handle, CURLOPT_PROGRESSFUNCTION, NULL);
1505 curl_easy_setopt(handler->handle, CURLOPT_VERBOSE, 0L);
1506 curl_easy_setopt(handler->handle, CURLOPT_DEBUGFUNCTION, NULL);
1507
1508 php_resource_factory_handle_dtor(handler->rf, handler->handle TSRMLS_CC);
1509 php_resource_factory_free(&handler->rf);
1510
1511 php_http_message_parser_free(&handler->request.parser);
1512 php_http_message_free(&handler->request.message);
1513 php_http_buffer_free(&handler->request.buffer);
1514 php_http_message_parser_free(&handler->response.parser);
1515 php_http_message_free(&handler->response.message);
1516 php_http_buffer_free(&handler->response.buffer);
1517 php_http_buffer_dtor(&handler->options.ranges);
1518 php_http_buffer_dtor(&handler->options.cookies);
1519 zend_hash_destroy(&handler->options.cache);
1520
1521 if (handler->options.headers) {
1522 curl_slist_free_all(handler->options.headers);
1523 handler->options.headers = NULL;
1524 }
1525
1526 efree(handler);
1527 }
1528
1529 static php_http_client_t *php_http_client_curl_init(php_http_client_t *h, void *handle)
1530 {
1531 php_http_client_curl_t *curl;
1532 TSRMLS_FETCH_FROM_CTX(h->ts);
1533
1534 if (!handle && !(handle = php_resource_factory_handle_ctor(h->rf, NULL TSRMLS_CC))) {
1535 php_http_error(HE_WARNING, PHP_HTTP_E_CLIENT_POOL, "Failed to initialize curl handle");
1536 return NULL;
1537 }
1538
1539 curl = ecalloc(1, sizeof(*curl));
1540 curl->handle = handle;
1541 curl->unfinished = 0;
1542 h->ctx = curl;
1543
1544 return h;
1545 }
1546
1547 static void php_http_client_curl_dtor(php_http_client_t *h)
1548 {
1549 php_http_client_curl_t *curl = h->ctx;
1550 TSRMLS_FETCH_FROM_CTX(h->ts);
1551
1552 #if PHP_HTTP_HAVE_EVENT
1553 if (curl->timeout) {
1554 efree(curl->timeout);
1555 curl->timeout = NULL;
1556 }
1557 #endif
1558 curl->unfinished = 0;
1559
1560 php_resource_factory_handle_dtor(h->rf, curl->handle TSRMLS_CC);
1561
1562 efree(curl);
1563 h->ctx = NULL;
1564 }
1565
1566 static void queue_dtor(php_http_client_enqueue_t *e)
1567 {
1568 php_http_client_curl_handler_t *handler = e->opaque;
1569
1570 if (handler->queue.dtor) {
1571 e->opaque = handler->queue.opaque;
1572 handler->queue.dtor(e);
1573 }
1574 php_http_client_curl_handler_dtor(handler);
1575 }
1576
1577 static php_resource_factory_t *create_rf(const char *url TSRMLS_DC)
1578 {
1579 php_url *purl;
1580 php_resource_factory_t *rf = NULL;
1581
1582 if ((purl = php_url_parse(url))) {
1583 char *id_str = NULL;
1584 size_t id_len = spprintf(&id_str, 0, "%s:%d", STR_PTR(purl->host), purl->port ? purl->port : 80);
1585 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);
1586
1587 if (pf) {
1588 rf = php_resource_factory_init(NULL, php_persistent_handle_get_resource_factory_ops(), pf, (void (*)(void*)) php_persistent_handle_abandon);
1589 }
1590
1591 php_url_free(purl);
1592 efree(id_str);
1593 }
1594
1595 if (!rf) {
1596 rf = php_resource_factory_init(NULL, &php_http_curle_resource_factory_ops, NULL, NULL);
1597 }
1598
1599 return rf;
1600 }
1601
1602 static STATUS php_http_client_curl_enqueue(php_http_client_t *h, php_http_client_enqueue_t *enqueue)
1603 {
1604 CURLMcode rs;
1605 php_http_client_curl_t *curl = h->ctx;
1606 php_http_client_curl_handler_t *handler;
1607 php_http_client_progress_state_t *progress;
1608 TSRMLS_FETCH_FROM_CTX(h->ts);
1609
1610 handler = php_http_client_curl_handler_init(h, create_rf(enqueue->request->http.info.request.url TSRMLS_CC));
1611 if (!handler) {
1612 return FAILURE;
1613 }
1614
1615 if (SUCCESS != php_http_client_curl_handler_prepare(handler, enqueue)) {
1616 php_http_client_curl_handler_dtor(handler);
1617 return FAILURE;
1618 }
1619
1620 handler->queue = *enqueue;
1621 enqueue->opaque = handler;
1622 enqueue->dtor = queue_dtor;
1623
1624 if (CURLM_OK == (rs = curl_multi_add_handle(curl->handle, handler->handle))) {
1625 zend_llist_add_element(&h->requests, enqueue);
1626 ++curl->unfinished;
1627
1628 if (h->callback.progress.func && SUCCESS == php_http_client_getopt(h, PHP_HTTP_CLIENT_OPT_PROGRESS_INFO, enqueue->request, &progress)) {
1629 progress->info = "start";
1630 h->callback.progress.func(h->callback.progress.arg, h, &handler->queue, progress);
1631 progress->started = 1;
1632 }
1633
1634 return SUCCESS;
1635 } else {
1636 php_http_error(HE_WARNING, PHP_HTTP_E_CLIENT_POOL, "Could not enqueue request: %s", curl_multi_strerror(rs));
1637 return FAILURE;
1638 }
1639 }
1640
1641 static STATUS php_http_client_curl_dequeue(php_http_client_t *h, php_http_client_enqueue_t *enqueue)
1642 {
1643 CURLMcode rs;
1644 php_http_client_curl_t *curl = h->ctx;
1645 php_http_client_curl_handler_t *handler = enqueue->opaque;
1646 TSRMLS_FETCH_FROM_CTX(h->ts);
1647
1648 if (CURLM_OK == (rs = curl_multi_remove_handle(curl->handle, handler->handle))) {
1649 zend_llist_del_element(&h->requests, handler->handle, (int (*)(void *, void *)) compare_queue);
1650 return SUCCESS;
1651 } else {
1652 php_http_error(HE_WARNING, PHP_HTTP_E_CLIENT, "Could not dequeue request: %s", curl_multi_strerror(rs));
1653 }
1654
1655 return FAILURE;
1656 }
1657
1658 static void php_http_client_curl_reset(php_http_client_t *h)
1659 {
1660 zend_llist_element *next_el, *this_el;
1661
1662 for (this_el = h->requests.head; this_el; this_el = next_el) {
1663 next_el = this_el->next;
1664 php_http_client_curl_dequeue(h, (void *) this_el->data);
1665 }
1666 }
1667
1668 #ifdef PHP_WIN32
1669 # define SELECT_ERROR SOCKET_ERROR
1670 #else
1671 # define SELECT_ERROR -1
1672 #endif
1673
1674 static STATUS php_http_client_curl_wait(php_http_client_t *h, struct timeval *custom_timeout)
1675 {
1676 int MAX;
1677 fd_set R, W, E;
1678 struct timeval timeout;
1679 php_http_client_curl_t *curl = h->ctx;
1680
1681 #if PHP_HTTP_HAVE_EVENT
1682 if (curl->useevents) {
1683 TSRMLS_FETCH_FROM_CTX(h->ts);
1684
1685 php_http_error(HE_WARNING, PHP_HTTP_E_RUNTIME, "not implemented");
1686 return FAILURE;
1687 }
1688 #endif
1689
1690 FD_ZERO(&R);
1691 FD_ZERO(&W);
1692 FD_ZERO(&E);
1693
1694 if (CURLM_OK == curl_multi_fdset(curl->handle, &R, &W, &E, &MAX)) {
1695 if (custom_timeout && timerisset(custom_timeout)) {
1696 timeout = *custom_timeout;
1697 } else {
1698 long max_tout = 1000;
1699
1700 if ((CURLM_OK == curl_multi_timeout(curl->handle, &max_tout)) && (max_tout > 0)) {
1701 timeout.tv_sec = max_tout / 1000;
1702 timeout.tv_usec = (max_tout % 1000) * 1000;
1703 } else {
1704 timeout.tv_sec = 0;
1705 timeout.tv_usec = 1000;
1706 }
1707 }
1708
1709 if (MAX == -1) {
1710 php_http_sleep((double) timeout.tv_sec + (double) (timeout.tv_usec / PHP_HTTP_MCROSEC));
1711 return SUCCESS;
1712 } else if (SELECT_ERROR != select(MAX + 1, &R, &W, &E, &timeout)) {
1713 return SUCCESS;
1714 }
1715 }
1716 return FAILURE;
1717 }
1718
1719 static int php_http_client_curl_once(php_http_client_t *h)
1720 {
1721 php_http_client_curl_t *curl = h->ctx;
1722
1723 #if PHP_HTTP_HAVE_EVENT
1724 if (curl->useevents) {
1725 TSRMLS_FETCH_FROM_CTX(h->ts);
1726 php_http_error(HE_WARNING, PHP_HTTP_E_RUNTIME, "not implemented");
1727 return FAILURE;
1728 }
1729 #endif
1730
1731 while (CURLM_CALL_MULTI_PERFORM == curl_multi_perform(curl->handle, &curl->unfinished));
1732
1733 php_http_curlm_responsehandler(h);
1734
1735 return curl->unfinished;
1736
1737 }
1738
1739 static STATUS php_http_client_curl_exec(php_http_client_t *h)
1740 {
1741 TSRMLS_FETCH_FROM_CTX(h->ts);
1742
1743 #if PHP_HTTP_HAVE_EVENT
1744 php_http_client_curl_t *curl = h->ctx;
1745
1746 if (curl->useevents) {
1747 php_http_curlm_timeout_callback(CURL_SOCKET_TIMEOUT, /*EV_READ|EV_WRITE*/0, h);
1748 do {
1749 int ev_rc = event_base_dispatch(PHP_HTTP_G->curl.event_base);
1750
1751 #if DBG_EVENTS
1752 fprintf(stderr, "%c", "X.0"[ev_rc+1]);
1753 #endif
1754
1755 if (ev_rc < 0) {
1756 php_http_error(HE_ERROR, PHP_HTTP_E_RUNTIME, "Error in event_base_dispatch()");
1757 return FAILURE;
1758 }
1759 } while (curl->unfinished);
1760 } else
1761 #endif
1762 {
1763 while (php_http_client_curl_once(h)) {
1764 if (SUCCESS != php_http_client_curl_wait(h, NULL)) {
1765 #ifdef PHP_WIN32
1766 /* see http://msdn.microsoft.com/library/en-us/winsock/winsock/windows_sockets_error_codes_2.asp */
1767 php_http_error(HE_WARNING, PHP_HTTP_E_SOCKET, "WinSock error: %d", WSAGetLastError());
1768 #else
1769 php_http_error(HE_WARNING, PHP_HTTP_E_SOCKET, strerror(errno));
1770 #endif
1771 return FAILURE;
1772 }
1773 }
1774 }
1775
1776 return SUCCESS;
1777 }
1778
1779 static STATUS php_http_client_curl_setopt(php_http_client_t *h, php_http_client_setopt_opt_t opt, void *arg)
1780 {
1781 php_http_client_curl_t *curl = h->ctx;
1782
1783 switch (opt) {
1784 case PHP_HTTP_CLIENT_OPT_ENABLE_PIPELINING:
1785 if (CURLM_OK != curl_multi_setopt(curl->handle, CURLMOPT_PIPELINING, (long) *((zend_bool *) arg))) {
1786 return FAILURE;
1787 }
1788 break;
1789
1790 case PHP_HTTP_CLIENT_OPT_USE_EVENTS:
1791 #if PHP_HTTP_HAVE_EVENT
1792 if ((curl->useevents = *((zend_bool *) arg))) {
1793 if (!curl->timeout) {
1794 curl->timeout = ecalloc(1, sizeof(struct event));
1795 }
1796 curl_multi_setopt(curl->handle, CURLMOPT_SOCKETDATA, h);
1797 curl_multi_setopt(curl->handle, CURLMOPT_SOCKETFUNCTION, php_http_curlm_socket_callback);
1798 curl_multi_setopt(curl->handle, CURLMOPT_TIMERDATA, h);
1799 curl_multi_setopt(curl->handle, CURLMOPT_TIMERFUNCTION, php_http_curlm_timer_callback);
1800 } else {
1801 curl_multi_setopt(curl->handle, CURLMOPT_SOCKETDATA, NULL);
1802 curl_multi_setopt(curl->handle, CURLMOPT_SOCKETFUNCTION, NULL);
1803 curl_multi_setopt(curl->handle, CURLMOPT_TIMERDATA, NULL);
1804 curl_multi_setopt(curl->handle, CURLMOPT_TIMERFUNCTION, NULL);
1805 }
1806 break;
1807 #endif
1808
1809 default:
1810 return FAILURE;
1811 }
1812 return SUCCESS;
1813 }
1814
1815 static STATUS php_http_client_curl_getopt(php_http_client_t *h, php_http_client_getopt_opt_t opt, void *arg, void **res)
1816 {
1817 php_http_client_enqueue_t *enqueue;
1818
1819 switch (opt) {
1820 case PHP_HTTP_CLIENT_OPT_PROGRESS_INFO:
1821 if ((enqueue = php_http_client_enqueued(h, arg, NULL))) {
1822 php_http_client_curl_handler_t *handler = enqueue->opaque;
1823
1824 *((php_http_client_progress_state_t **) res) = &handler->progress;
1825 return SUCCESS;
1826 }
1827 break;
1828
1829 case PHP_HTTP_CLIENT_OPT_TRANSFER_INFO:
1830 if ((enqueue = php_http_client_enqueued(h, arg, NULL))) {
1831 php_http_client_curl_handler_t *handler = enqueue->opaque;
1832
1833 php_http_curle_get_info(handler->handle, *(HashTable **) res);
1834 return SUCCESS;
1835 }
1836 break;
1837
1838 default:
1839 break;
1840 }
1841
1842 return FAILURE;
1843 }
1844
1845 static php_http_client_ops_t php_http_client_curl_ops = {
1846 &php_http_curlm_resource_factory_ops,
1847 php_http_client_curl_init,
1848 NULL /* copy */,
1849 php_http_client_curl_dtor,
1850 php_http_client_curl_reset,
1851 php_http_client_curl_exec,
1852 php_http_client_curl_wait,
1853 php_http_client_curl_once,
1854 php_http_client_curl_enqueue,
1855 php_http_client_curl_dequeue,
1856 php_http_client_curl_setopt,
1857 php_http_client_curl_getopt
1858 };
1859
1860 PHP_HTTP_API php_http_client_ops_t *php_http_client_curl_get_ops(void)
1861 {
1862 return &php_http_client_curl_ops;
1863 }
1864
1865 PHP_MINIT_FUNCTION(http_client_curl)
1866 {
1867 php_http_options_t *options;
1868 php_http_client_driver_t driver = {
1869 ZEND_STRL("curl"),
1870 &php_http_client_curl_ops
1871 };
1872
1873 if (SUCCESS != php_http_client_driver_add(&driver)) {
1874 return FAILURE;
1875 }
1876
1877 if (SUCCESS != php_persistent_handle_provide(ZEND_STRL("http\\Client\\Curl"), &php_http_curlm_resource_factory_ops, NULL, NULL TSRMLS_CC)) {
1878 return FAILURE;
1879 }
1880 if (SUCCESS != php_persistent_handle_provide(ZEND_STRL("http\\Client\\Curl\\Request"), &php_http_curle_resource_factory_ops, NULL, NULL TSRMLS_CC)) {
1881 return FAILURE;
1882 }
1883
1884 if ((options = php_http_options_init(&php_http_curle_options, 1))) {
1885 options->getter = php_http_curle_get_option;
1886 options->setter = php_http_curle_set_option;
1887
1888 php_http_curle_options_init(options TSRMLS_CC);
1889 }
1890
1891 /*
1892 * HTTP Protocol Version Constants
1893 */
1894 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "HTTP_VERSION_1_0", CURL_HTTP_VERSION_1_0, CONST_CS|CONST_PERSISTENT);
1895 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "HTTP_VERSION_1_1", CURL_HTTP_VERSION_1_1, CONST_CS|CONST_PERSISTENT);
1896 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "HTTP_VERSION_ANY", CURL_HTTP_VERSION_NONE, CONST_CS|CONST_PERSISTENT);
1897
1898 /*
1899 * SSL Version Constants
1900 */
1901 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "SSL_VERSION_TLSv1", CURL_SSLVERSION_TLSv1, CONST_CS|CONST_PERSISTENT);
1902 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "SSL_VERSION_SSLv2", CURL_SSLVERSION_SSLv2, CONST_CS|CONST_PERSISTENT);
1903 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "SSL_VERSION_SSLv3", CURL_SSLVERSION_SSLv3, CONST_CS|CONST_PERSISTENT);
1904 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "SSL_VERSION_ANY", CURL_SSLVERSION_DEFAULT, CONST_CS|CONST_PERSISTENT);
1905
1906 /*
1907 * DNS IPvX resolving
1908 */
1909 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "IPRESOLVE_V4", CURL_IPRESOLVE_V4, CONST_CS|CONST_PERSISTENT);
1910 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "IPRESOLVE_V6", CURL_IPRESOLVE_V6, CONST_CS|CONST_PERSISTENT);
1911 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "IPRESOLVE_ANY", CURL_IPRESOLVE_WHATEVER, CONST_CS|CONST_PERSISTENT);
1912
1913 /*
1914 * Auth Constants
1915 */
1916 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "AUTH_BASIC", CURLAUTH_BASIC, CONST_CS|CONST_PERSISTENT);
1917 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "AUTH_DIGEST", CURLAUTH_DIGEST, CONST_CS|CONST_PERSISTENT);
1918 #if PHP_HTTP_CURL_VERSION(7,19,3)
1919 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "AUTH_DIGEST_IE", CURLAUTH_DIGEST_IE, CONST_CS|CONST_PERSISTENT);
1920 #endif
1921 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "AUTH_NTLM", CURLAUTH_NTLM, CONST_CS|CONST_PERSISTENT);
1922 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "AUTH_GSSNEG", CURLAUTH_GSSNEGOTIATE, CONST_CS|CONST_PERSISTENT);
1923 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "AUTH_ANY", CURLAUTH_ANY, CONST_CS|CONST_PERSISTENT);
1924
1925 /*
1926 * Proxy Type Constants
1927 */
1928 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "PROXY_SOCKS4", CURLPROXY_SOCKS4, CONST_CS|CONST_PERSISTENT);
1929 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "PROXY_SOCKS4A", CURLPROXY_SOCKS5, CONST_CS|CONST_PERSISTENT);
1930 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "PROXY_SOCKS5_HOSTNAME", CURLPROXY_SOCKS5, CONST_CS|CONST_PERSISTENT);
1931 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "PROXY_SOCKS5", CURLPROXY_SOCKS5, CONST_CS|CONST_PERSISTENT);
1932 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "PROXY_HTTP", CURLPROXY_HTTP, CONST_CS|CONST_PERSISTENT);
1933 # if PHP_HTTP_CURL_VERSION(7,19,4)
1934 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "PROXY_HTTP_1_0", CURLPROXY_HTTP_1_0, CONST_CS|CONST_PERSISTENT);
1935 # endif
1936
1937 /*
1938 * Post Redirection Constants
1939 */
1940 #if PHP_HTTP_CURL_VERSION(7,19,1)
1941 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "POSTREDIR_301", CURL_REDIR_POST_301, CONST_CS|CONST_PERSISTENT);
1942 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "POSTREDIR_302", CURL_REDIR_POST_302, CONST_CS|CONST_PERSISTENT);
1943 REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "POSTREDIR_ALL", CURL_REDIR_POST_ALL, CONST_CS|CONST_PERSISTENT);
1944 #endif
1945
1946 return SUCCESS;
1947 }
1948
1949 PHP_MSHUTDOWN_FUNCTION(http_client_curl)
1950 {
1951 php_http_options_dtor(&php_http_curle_options);
1952 return SUCCESS;
1953 }
1954
1955 #if PHP_HTTP_HAVE_EVENT
1956 PHP_RINIT_FUNCTION(http_client_curl)
1957 {
1958 if (!PHP_HTTP_G->curl.event_base && !(PHP_HTTP_G->curl.event_base = event_base_new())) {
1959 return FAILURE;
1960 }
1961 return SUCCESS;
1962 }
1963 PHP_RSHUTDOWN_FUNCTION(http_client_curl)
1964 {
1965 if (PHP_HTTP_G->curl.event_base) {
1966 event_base_free(PHP_HTTP_G->curl.event_base);
1967 PHP_HTTP_G->curl.event_base = NULL;
1968 }
1969 return SUCCESS;
1970 }
1971 #endif /* PHP_HTTP_HAVE_EVENT */
1972
1973 #endif /* PHP_HTTP_HAVE_CURL */
1974
1975 /*
1976 * Local variables:
1977 * tab-width: 4
1978 * c-basic-offset: 4
1979 * End:
1980 * vim600: noet sw=4 ts=4 fdm=marker
1981 * vim<600: noet sw=4 ts=4
1982 */