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