- crunch ARG_INFO
[m6w6/ext-http] / http_request_api.c
1 /*
2 +----------------------------------------------------------------------+
3 | PECL :: http |
4 +----------------------------------------------------------------------+
5 | This source file is subject to version 3.0 of the PHP license, that |
6 | is bundled with this package in the file LICENSE, and is available |
7 | through the world-wide-web at http://www.php.net/license/3_0.txt. |
8 | If you did not receive a copy of the PHP license and are unable to |
9 | obtain it through the world-wide-web, please send a note to |
10 | license@php.net so we can mail you a copy immediately. |
11 +----------------------------------------------------------------------+
12 | Copyright (c) 2004-2005 Michael Wallner <mike@php.net> |
13 +----------------------------------------------------------------------+
14 */
15
16 /* $Id$ */
17
18 #ifdef HAVE_CONFIG_H
19 # include "config.h"
20 #endif
21
22 #ifdef PHP_WIN32
23 # include <winsock2.h>
24 #endif
25
26 #include <curl/curl.h>
27
28 #include "phpstr/phpstr.h"
29
30 #include "php.h"
31 #include "php_http.h"
32 #include "php_http_std_defs.h"
33 #include "php_http_api.h"
34 #include "php_http_request_api.h"
35 #include "php_http_request_object.h"
36 #include "php_http_requestpool_object.h"
37 #include "php_http_url_api.h"
38
39 #ifndef HTTP_CURL_USE_ZEND_MM
40 # define HTTP_CURL_USE_ZEND_MM 0
41 #endif
42
43 ZEND_EXTERN_MODULE_GLOBALS(http)
44
45 #if LIBCURL_VERSION_NUM < 0x070c00
46 # define curl_easy_strerror(code) HTTP_G(request).curl.error
47 #endif
48
49 #define HTTP_CURL_INFO(I) HTTP_CURL_INFO_EX(I, I)
50 #define HTTP_CURL_INFO_EX(I, X) \
51 switch (CURLINFO_ ##I & ~CURLINFO_MASK) \
52 { \
53 case CURLINFO_STRING: \
54 { \
55 char *c; \
56 if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_ ##I, &c)) { \
57 add_assoc_string(&array, pretty_key(http_request_data_copy(COPY_STRING, #X), sizeof(#X)-1, 0, 0), c ? c : "", 1); \
58 } \
59 } \
60 break; \
61 \
62 case CURLINFO_DOUBLE: \
63 { \
64 double d; \
65 if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_ ##I, &d)) { \
66 add_assoc_double(&array, pretty_key(http_request_data_copy(COPY_STRING, #X), sizeof(#X)-1, 0, 0), d); \
67 } \
68 } \
69 break; \
70 \
71 case CURLINFO_LONG: \
72 { \
73 long l; \
74 if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_ ##I, &l)) { \
75 add_assoc_long(&array, pretty_key(http_request_data_copy(COPY_STRING, #X), sizeof(#X)-1, 0, 0), l); \
76 } \
77 } \
78 break; \
79 }
80
81 #define HTTP_CURL_OPT(OPTION, p) curl_easy_setopt(ch, CURLOPT_##OPTION, (p))
82 #define HTTP_CURL_OPT_STRING(keyname) HTTP_CURL_OPT_STRING_EX(keyname, keyname)
83 #define HTTP_CURL_OPT_SSL_STRING(keyname) HTTP_CURL_OPT_STRING_EX(keyname, SSL##keyname)
84 #define HTTP_CURL_OPT_SSL_STRING_(keyname) HTTP_CURL_OPT_STRING_EX(keyname, SSL_##keyname)
85 #define HTTP_CURL_OPT_STRING_EX(keyname, optname) \
86 if (!strcasecmp(key, #keyname)) { \
87 convert_to_string_ex(param); \
88 HTTP_CURL_OPT(optname, http_request_data_copy(COPY_STRING, Z_STRVAL_PP(param))); \
89 key = NULL; \
90 continue; \
91 }
92 #define HTTP_CURL_OPT_LONG(keyname) HTTP_OPT_SSL_LONG_EX(keyname, keyname)
93 #define HTTP_CURL_OPT_SSL_LONG(keyname) HTTP_CURL_OPT_LONG_EX(keyname, SSL##keyname)
94 #define HTTP_CURL_OPT_SSL_LONG_(keyname) HTTP_CURL_OPT_LONG_EX(keyname, SSL_##keyname)
95 #define HTTP_CURL_OPT_LONG_EX(keyname, optname) \
96 if (!strcasecmp(key, #keyname)) { \
97 convert_to_long_ex(param); \
98 HTTP_CURL_OPT(optname, Z_LVAL_PP(param)); \
99 key = NULL; \
100 continue; \
101 }
102
103
104 static const char *const http_request_methods[HTTP_MAX_REQUEST_METHOD + 1];
105 #define http_curl_getopt(o, k, t) _http_curl_getopt_ex((o), (k), sizeof(k), (t) TSRMLS_CC)
106 #define http_curl_getopt_ex(o, k, l, t) _http_curl_getopt_ex((o), (k), (l), (t) TSRMLS_CC)
107 static inline zval *_http_curl_getopt_ex(HashTable *options, char *key, size_t keylen, int type TSRMLS_DC);
108 static size_t http_curl_write_callback(char *, size_t, size_t, void *);
109 static size_t http_curl_read_callback(void *, size_t, size_t, void *);
110 static int http_curl_progress_callback(void *, double, double, double, double);
111 static int http_curl_debug_callback(CURL *, curl_infotype, char *, size_t, void *);
112
113 typedef struct {
114 void ***tsrm_ctx;
115 void *data;
116 } http_curl_callback_ctx;
117
118 #define HTTP_CURL_CALLBACK_DATA(from, type, var) \
119 http_curl_callback_ctx *__CTX = (http_curl_callback_ctx *) (from); \
120 TSRMLS_FETCH_FROM_CTX(__CTX->tsrm_ctx); \
121 type (var) = (type) (__CTX->data)
122
123 #define http_curl_callback_data(data) _http_curl_callback_data((data) TSRMLS_CC)
124 static http_curl_callback_ctx *_http_curl_callback_data(void *data TSRMLS_DC);
125
126 static void http_request_pool_freebody(http_request_body **body);
127 static void http_request_pool_responsehandler(zval **req TSRMLS_DC);
128 static inline STATUS http_request_pool_select(http_request_pool *pool);
129 static inline void http_request_pool_perform(http_request_pool *pool);
130
131 #if HTTP_CURL_USE_ZEND_MM
132 static void http_curl_free(void *p) { efree(p); }
133 static char *http_curl_strdup(const char *p) { return estrdup(p); }
134 static void *http_curl_malloc(size_t s) { return emalloc(s); }
135 static void *http_curl_realloc(void *p, size_t s) { return erealloc(p, s); }
136 static void *http_curl_calloc(size_t n, size_t s) { return ecalloc(n, s); }
137 #endif
138
139 /* {{{ STATUS http_request_global_init() */
140 STATUS _http_request_global_init(void)
141 {
142 #if HTTP_CURL_USE_ZEND_MM
143 if (CURLE_OK != curl_global_init_mem(CURL_GLOBAL_ALL,
144 http_curl_malloc,
145 http_curl_free,
146 http_curl_realloc,
147 http_curl_strdup,
148 http_curl_calloc)) {
149 return FAILURE;
150 }
151 #else
152 if (CURLE_OK != curl_global_init(CURL_GLOBAL_ALL)) {
153 return FAILURE;
154 }
155 #endif
156 return SUCCESS;
157 }
158 /* }}} */
159
160 /* {{{ void *http_request_data_copy(int, void *) */
161 void *_http_request_data_copy(int type, void *data TSRMLS_DC)
162 {
163 switch (type)
164 {
165 case COPY_STRING:
166 {
167 char *new_str = estrdup((const char*) data);
168 zend_llist_add_element(&HTTP_G(request).copies.strings, &new_str);
169 return new_str;
170 }
171
172 case COPY_SLIST:
173 {
174 zend_llist_add_element(&HTTP_G(request).copies.slists, &data);
175 return data;
176 }
177
178 default:
179 {
180 return data;
181 }
182 }
183 }
184 /* }}} */
185
186 /* {{{ void http_request_data_free_string(char **) */
187 void _http_request_data_free_string(void *string)
188 {
189 efree(*((char **)string));
190 }
191 /* }}} */
192
193 /* {{{ void http_request_data_free_slist(struct curl_slist **) */
194 void _http_request_data_free_slist(void *list)
195 {
196 curl_slist_free_all(*((struct curl_slist **) list));
197 }
198 /* }}} */
199
200 /* {{{ http_request_body *http_request_body_new() */
201 PHP_HTTP_API http_request_body *_http_request_body_new(TSRMLS_D)
202 {
203 http_request_body *body = ecalloc(1, sizeof(http_request_body));
204 return body;
205 }
206 /* }}} */
207
208 /* {{{ STATUS http_request_body_fill(http_request_body *body, HashTable *, HashTable *) */
209 PHP_HTTP_API STATUS _http_request_body_fill(http_request_body *body, HashTable *fields, HashTable *files TSRMLS_DC)
210 {
211 if (files && (zend_hash_num_elements(files) > 0)) {
212 char *key = NULL;
213 ulong idx;
214 zval **data;
215 struct curl_httppost *http_post_data[2] = {NULL, NULL};
216
217 /* normal data */
218 FOREACH_HASH_KEYVAL(fields, key, idx, data) {
219 CURLcode err;
220 if (key) {
221 convert_to_string_ex(data);
222 err = curl_formadd(&http_post_data[0], &http_post_data[1],
223 CURLFORM_COPYNAME, key,
224 CURLFORM_COPYCONTENTS, Z_STRVAL_PP(data),
225 CURLFORM_CONTENTSLENGTH, Z_STRLEN_PP(data),
226 CURLFORM_END
227 );
228 if (CURLE_OK != err) {
229 http_error_ex(E_WARNING, HTTP_E_CURL, "Could not encode post fields: %s", curl_easy_strerror(err));
230 curl_formfree(http_post_data[0]);
231 return FAILURE;
232 }
233
234 /* reset */
235 key = NULL;
236 }
237 }
238
239 /* file data */
240 FOREACH_HASH_VAL(files, data) {
241 CURLcode err;
242 zval **file, **type, **name;
243 if ( SUCCESS == zend_hash_find(Z_ARRVAL_PP(data), "name", sizeof("name"), (void **) &name) &&
244 SUCCESS == zend_hash_find(Z_ARRVAL_PP(data), "type", sizeof("type"), (void **) &type) &&
245 SUCCESS == zend_hash_find(Z_ARRVAL_PP(data), "file", sizeof("file"), (void **) &file)) {
246 err = curl_formadd(&http_post_data[0], &http_post_data[1],
247 CURLFORM_COPYNAME, Z_STRVAL_PP(name),
248 CURLFORM_FILE, Z_STRVAL_PP(file),
249 CURLFORM_CONTENTTYPE, Z_STRVAL_PP(type),
250 CURLFORM_END
251 );
252 if (CURLE_OK != err) {
253 http_error_ex(E_WARNING, HTTP_E_CURL, "Could not encode post files: %s", curl_easy_strerror(err));
254 curl_formfree(http_post_data[0]);
255 return FAILURE;
256 }
257 } else {
258 http_error(E_NOTICE, HTTP_E_PARAM, "Post file array entry misses either 'name', 'type' or 'file' entry");
259 }
260 }
261
262 body->type = HTTP_REQUEST_BODY_CURLPOST;
263 body->data = http_post_data[0];
264 body->size = 0;
265
266 } else {
267 char *encoded;
268 size_t encoded_len;
269
270 if (SUCCESS != http_urlencode_hash_ex(fields, 1, NULL, 0, &encoded, &encoded_len)) {
271 http_error(E_WARNING, HTTP_E_ENCODE, "Could not encode post data");
272 return FAILURE;
273 }
274
275 body->type = HTTP_REQUEST_BODY_CSTRING;
276 body->data = encoded;
277 body->size = encoded_len;
278 }
279
280 return SUCCESS;
281 }
282 /* }}} */
283
284 /* {{{ void http_request_body_dtor(http_request_body *) */
285 PHP_HTTP_API void _http_request_body_dtor(http_request_body *body TSRMLS_DC)
286 {
287 if (body) {
288 switch (body->type)
289 {
290 case HTTP_REQUEST_BODY_CSTRING:
291 if (body->data) {
292 efree(body->data);
293 }
294 break;
295
296 case HTTP_REQUEST_BODY_CURLPOST:
297 curl_formfree(body->data);
298 break;
299
300 case HTTP_REQUEST_BODY_UPLOADFILE:
301 php_stream_close(body->data);
302 break;
303 }
304 }
305 }
306 /* }}} */
307
308 /* {{{ void http_request_body_free(http_request_body *) */
309 PHP_HTTP_API void _http_request_body_free(http_request_body *body TSRMLS_DC)
310 {
311 if (body) {
312 http_request_body_dtor(body);
313 efree(body);
314 }
315 }
316 /* }}} */
317
318 /* {{{ STATUS http_request_init(CURL *, http_request_method, char *, http_request_body *, HashTable *, phpstr *) */
319 PHP_HTTP_API STATUS _http_request_init(CURL *ch, http_request_method meth, const char *url, http_request_body *body, HashTable *options, phpstr *response TSRMLS_DC)
320 {
321 zval *zoption;
322 zend_bool range_req = 0;
323
324 /* reset CURL handle */
325 #if LIBCURL_VERSION_NUM >= 0x070c01
326 curl_easy_reset(ch);
327 #endif
328
329 /* set options */
330 if (url) {
331 HTTP_CURL_OPT(URL, http_request_data_copy(COPY_STRING, (void *) url));
332 }
333
334 if (response) {
335 HTTP_CURL_OPT(WRITEDATA, http_curl_callback_data(response));
336 HTTP_CURL_OPT(WRITEHEADER, http_curl_callback_data(response));
337 }
338
339 HTTP_CURL_OPT(HEADER, 0);
340 HTTP_CURL_OPT(FILETIME, 1);
341 HTTP_CURL_OPT(AUTOREFERER, 1);
342 HTTP_CURL_OPT(READFUNCTION, http_curl_read_callback);
343 HTTP_CURL_OPT(WRITEFUNCTION, http_curl_write_callback);
344 HTTP_CURL_OPT(HEADERFUNCTION, http_curl_write_callback);
345
346 #if defined(ZTS) && (LIBCURL_VERSION_NUM >= 0x070a00)
347 HTTP_CURL_OPT(NOSIGNAL, 1);
348 #endif
349 #if LIBCURL_VERSION_NUM < 0x070c00
350 HTTP_CURL_OPT(ERRORBUFFER, HTTP_G(request).curl.error);
351 #endif
352
353 /* progress callback */
354 if (zoption = http_curl_getopt(options, "onprogress", 0)) {
355 HTTP_CURL_OPT(PROGRESSFUNCTION, http_curl_progress_callback);
356 HTTP_CURL_OPT(PROGRESSDATA, http_curl_callback_data(zoption));
357 } else {
358 HTTP_CURL_OPT(NOPROGRESS, 1);
359 }
360
361 /* debug callback */
362 if (zoption = http_curl_getopt(options, "ondebug", 0)) {
363 HTTP_CURL_OPT(VERBOSE, 1);
364 HTTP_CURL_OPT(DEBUGFUNCTION, http_curl_debug_callback);
365 HTTP_CURL_OPT(DEBUGDATA, http_curl_callback_data(zoption));
366 } else {
367 HTTP_CURL_OPT(VERBOSE, 0);
368 }
369
370 /* proxy */
371 if (zoption = http_curl_getopt(options, "proxyhost", IS_STRING)) {
372 HTTP_CURL_OPT(PROXY, http_request_data_copy(COPY_STRING, Z_STRVAL_P(zoption)));
373 /* port */
374 if (zoption = http_curl_getopt(options, "proxyport", IS_LONG)) {
375 HTTP_CURL_OPT(PROXYPORT, Z_LVAL_P(zoption));
376 }
377 /* user:pass */
378 if (zoption = http_curl_getopt(options, "proxyauth", IS_STRING)) {
379 HTTP_CURL_OPT(PROXYUSERPWD, http_request_data_copy(COPY_STRING, Z_STRVAL_P(zoption)));
380 }
381 #if LIBCURL_VERSION_NUM >= 0x070a07
382 /* auth method */
383 if (zoption = http_curl_getopt(options, "proxyauthtype", IS_LONG)) {
384 HTTP_CURL_OPT(PROXYAUTH, Z_LVAL_P(zoption));
385 }
386 #endif
387 }
388
389 /* outgoing interface */
390 if (zoption = http_curl_getopt(options, "interface", IS_STRING)) {
391 HTTP_CURL_OPT(INTERFACE, http_request_data_copy(COPY_STRING, Z_STRVAL_P(zoption)));
392 }
393
394 /* another port */
395 if (zoption = http_curl_getopt(options, "port", IS_LONG)) {
396 HTTP_CURL_OPT(PORT, Z_LVAL_P(zoption));
397 }
398
399 /* auth */
400 if (zoption = http_curl_getopt(options, "httpauth", IS_STRING)) {
401 HTTP_CURL_OPT(USERPWD, http_request_data_copy(COPY_STRING, Z_STRVAL_P(zoption)));
402 }
403 #if LIBCURL_VERSION_NUM >= 0x070a06
404 if (zoption = http_curl_getopt(options, "httpauthtype", IS_LONG)) {
405 HTTP_CURL_OPT(HTTPAUTH, Z_LVAL_P(zoption));
406 }
407 #endif
408
409 /* compress, empty string enables deflate and gzip */
410 if (zoption = http_curl_getopt(options, "compress", IS_BOOL)) {
411 if (Z_LVAL_P(zoption)) {
412 HTTP_CURL_OPT(ENCODING, http_request_data_copy(COPY_STRING, ""));
413 }
414 }
415
416 /* redirects, defaults to 0 */
417 if (zoption = http_curl_getopt(options, "redirect", IS_LONG)) {
418 HTTP_CURL_OPT(FOLLOWLOCATION, Z_LVAL_P(zoption) ? 1 : 0);
419 HTTP_CURL_OPT(MAXREDIRS, Z_LVAL_P(zoption));
420 if (zoption = http_curl_getopt(options, "unrestrictedauth", IS_BOOL)) {
421 HTTP_CURL_OPT(UNRESTRICTED_AUTH, Z_LVAL_P(zoption));
422 }
423 } else {
424 HTTP_CURL_OPT(FOLLOWLOCATION, 0);
425 }
426
427 /* referer */
428 if (zoption = http_curl_getopt(options, "referer", IS_STRING)) {
429 HTTP_CURL_OPT(REFERER, http_request_data_copy(COPY_STRING, Z_STRVAL_P(zoption)));
430 }
431
432 /* useragent, default "PECL::HTTP/version (PHP/version)" */
433 if (zoption = http_curl_getopt(options, "useragent", IS_STRING)) {
434 HTTP_CURL_OPT(USERAGENT, http_request_data_copy(COPY_STRING, Z_STRVAL_P(zoption)));
435 } else {
436 HTTP_CURL_OPT(USERAGENT, http_request_data_copy(COPY_STRING, "PECL::HTTP/" HTTP_PEXT_VERSION " (PHP/" PHP_VERSION ")"));
437 }
438
439 /* additional headers, array('name' => 'value') */
440 if (zoption = http_curl_getopt(options, "headers", IS_ARRAY)) {
441 char *header_key;
442 ulong header_idx;
443 struct curl_slist *headers = NULL;
444
445 FOREACH_KEY(zoption, header_key, header_idx) {
446 if (header_key) {
447 zval **header_val;
448 if (SUCCESS == zend_hash_get_current_data(Z_ARRVAL_P(zoption), (void **) &header_val)) {
449 char header[1024] = {0};
450 snprintf(header, 1023, "%s: %s", header_key, Z_STRVAL_PP(header_val));
451 headers = curl_slist_append(headers, http_request_data_copy(COPY_STRING, header));
452 }
453
454 /* reset */
455 header_key = NULL;
456 }
457 }
458
459 if (headers) {
460 HTTP_CURL_OPT(HTTPHEADER, http_request_data_copy(COPY_SLIST, headers));
461 }
462 } else {
463 HTTP_CURL_OPT(HTTPHEADER, NULL);
464 }
465
466 /* cookies, array('name' => 'value') */
467 if (zoption = http_curl_getopt(options, "cookies", IS_ARRAY)) {
468 char *cookie_key = NULL;
469 ulong cookie_idx = 0;
470 phpstr *qstr = phpstr_new();
471
472 FOREACH_KEY(zoption, cookie_key, cookie_idx) {
473 if (cookie_key) {
474 zval **cookie_val;
475 if (SUCCESS == zend_hash_get_current_data(Z_ARRVAL_P(zoption), (void **) &cookie_val)) {
476 phpstr_appendf(qstr, "%s=%s; ", cookie_key, Z_STRVAL_PP(cookie_val));
477 }
478
479 /* reset */
480 cookie_key = NULL;
481 }
482 }
483
484 if (qstr->used) {
485 phpstr_fix(qstr);
486 HTTP_CURL_OPT(COOKIE, http_request_data_copy(COPY_STRING, qstr->data));
487 }
488 phpstr_free(qstr);
489 }
490
491 /* cookiestore */
492 if (zoption = http_curl_getopt(options, "cookiestore", IS_STRING)) {
493 HTTP_CURL_OPT(COOKIEFILE, http_request_data_copy(COPY_STRING, Z_STRVAL_P(zoption)));
494 HTTP_CURL_OPT(COOKIEJAR, http_request_data_copy(COPY_STRING, Z_STRVAL_P(zoption)));
495 }
496
497 /* resume */
498 if (zoption = http_curl_getopt(options, "resume", IS_LONG)) {
499 range_req = 1;
500 HTTP_CURL_OPT(RESUME_FROM, Z_LVAL_P(zoption));
501 }
502
503 /* maxfilesize */
504 if (zoption = http_curl_getopt(options, "maxfilesize", IS_LONG)) {
505 HTTP_CURL_OPT(MAXFILESIZE, Z_LVAL_P(zoption));
506 }
507
508 /* lastmodified */
509 if (zoption = http_curl_getopt(options, "lastmodified", IS_LONG)) {
510 HTTP_CURL_OPT(TIMECONDITION, range_req ? CURL_TIMECOND_IFUNMODSINCE : CURL_TIMECOND_IFMODSINCE);
511 HTTP_CURL_OPT(TIMEVALUE, Z_LVAL_P(zoption));
512 }
513
514 /* timeout */
515 if (zoption = http_curl_getopt(options, "timeout", IS_LONG)) {
516 HTTP_CURL_OPT(TIMEOUT, Z_LVAL_P(zoption));
517 }
518
519 /* connecttimeout, defaults to 1 */
520 if (zoption = http_curl_getopt(options, "connecttimeout", IS_LONG)) {
521 HTTP_CURL_OPT(CONNECTTIMEOUT, Z_LVAL_P(zoption));
522 } else {
523 HTTP_CURL_OPT(CONNECTTIMEOUT, 1);
524 }
525
526 /* ssl */
527 if (zoption = http_curl_getopt(options, "ssl", IS_ARRAY)) {
528 ulong idx;
529 char *key = NULL;
530 zval **param;
531
532 FOREACH_KEYVAL(zoption, key, idx, param) {
533 if (key) {
534 HTTP_CURL_OPT_SSL_STRING(CERT);
535 #if LIBCURL_VERSION_NUM >= 0x070903
536 HTTP_CURL_OPT_SSL_STRING(CERTTYPE);
537 #endif
538 HTTP_CURL_OPT_SSL_STRING(CERTPASSWD);
539
540 HTTP_CURL_OPT_SSL_STRING(KEY);
541 HTTP_CURL_OPT_SSL_STRING(KEYTYPE);
542 HTTP_CURL_OPT_SSL_STRING(KEYPASSWD);
543
544 HTTP_CURL_OPT_SSL_STRING(ENGINE);
545 HTTP_CURL_OPT_SSL_LONG(VERSION);
546
547 HTTP_CURL_OPT_SSL_LONG_(VERIFYPEER);
548 HTTP_CURL_OPT_SSL_LONG_(VERIFYHOST);
549 HTTP_CURL_OPT_SSL_STRING_(CIPHER_LIST);
550
551
552 HTTP_CURL_OPT_STRING(CAINFO);
553 #if LIBCURL_VERSION_NUM >= 0x070908
554 HTTP_CURL_OPT_STRING(CAPATH);
555 #endif
556 HTTP_CURL_OPT_STRING(RANDOM_FILE);
557 HTTP_CURL_OPT_STRING(EGDSOCKET);
558
559 /* reset key */
560 key = NULL;
561 }
562 }
563 } else {
564 /* disable SSL verification by default */
565 HTTP_CURL_OPT(SSL_VERIFYPEER, 0);
566 HTTP_CURL_OPT(SSL_VERIFYHOST, 0);
567 }
568
569 /* request method */
570 switch (meth)
571 {
572 case HTTP_GET:
573 curl_easy_setopt(ch, CURLOPT_HTTPGET, 1);
574 break;
575
576 case HTTP_HEAD:
577 curl_easy_setopt(ch, CURLOPT_NOBODY, 1);
578 break;
579
580 case HTTP_POST:
581 curl_easy_setopt(ch, CURLOPT_POST, 1);
582 break;
583
584 case HTTP_PUT:
585 curl_easy_setopt(ch, CURLOPT_UPLOAD, 1);
586 break;
587
588 default:
589 if (http_request_method_exists(0, meth, NULL)) {
590 curl_easy_setopt(ch, CURLOPT_CUSTOMREQUEST, http_request_method_name(meth));
591 } else {
592 http_error_ex(E_WARNING, HTTP_E_CURL, "Unsupported request method: %d", meth);
593 return FAILURE;
594 }
595 break;
596 }
597
598 /* attach request body */
599 if (body && (meth != HTTP_GET) && (meth != HTTP_HEAD)) {
600 switch (body->type)
601 {
602 case HTTP_REQUEST_BODY_CSTRING:
603 curl_easy_setopt(ch, CURLOPT_POSTFIELDS, (char *) body->data);
604 curl_easy_setopt(ch, CURLOPT_POSTFIELDSIZE, body->size);
605 break;
606
607 case HTTP_REQUEST_BODY_CURLPOST:
608 curl_easy_setopt(ch, CURLOPT_HTTPPOST, (struct curl_httppost *) body->data);
609 break;
610
611 case HTTP_REQUEST_BODY_UPLOADFILE:
612 case HTTP_REQUEST_BODY_UPLOADDATA:
613 curl_easy_setopt(ch, CURLOPT_READDATA, body);
614 curl_easy_setopt(ch, CURLOPT_INFILESIZE, body->size);
615 break;
616
617 default:
618 http_error_ex(E_WARNING, HTTP_E_CURL, "Unknown request body type: %d", body->type);
619 return FAILURE;
620 break;
621 }
622 }
623
624 return SUCCESS;
625 }
626 /* }}} */
627
628 /* {{{ STATUS http_request_exec(CURL *, HashTable *) */
629 PHP_HTTP_API STATUS _http_request_exec(CURL *ch, HashTable *info TSRMLS_DC)
630 {
631 CURLcode result;
632
633 /* perform request */
634 if (CURLE_OK != (result = curl_easy_perform(ch))) {
635 http_error_ex(E_WARNING, HTTP_E_CURL, "Could not perform request: %s", curl_easy_strerror(result));
636 return FAILURE;
637 } else {
638 /* get curl info */
639 if (info) {
640 http_request_info(ch, info);
641 }
642 return SUCCESS;
643 }
644 }
645 /* }}} */
646
647 /* {{{ void http_request_info(CURL *, HashTable *) */
648 PHP_HTTP_API void _http_request_info(CURL *ch, HashTable *info TSRMLS_DC)
649 {
650 zval array;
651 Z_ARRVAL(array) = info;
652
653 HTTP_CURL_INFO(EFFECTIVE_URL);
654 #if LIBCURL_VERSION_NUM >= 0x070a07
655 HTTP_CURL_INFO(RESPONSE_CODE);
656 #else
657 HTTP_CURL_INFO_EX(HTTP_CODE, RESPONSE_CODE);
658 #endif
659 HTTP_CURL_INFO(HTTP_CONNECTCODE);
660 #if LIBCURL_VERSION_NUM >= 0x070500
661 HTTP_CURL_INFO(FILETIME);
662 #endif
663 HTTP_CURL_INFO(TOTAL_TIME);
664 HTTP_CURL_INFO(NAMELOOKUP_TIME);
665 HTTP_CURL_INFO(CONNECT_TIME);
666 HTTP_CURL_INFO(PRETRANSFER_TIME);
667 HTTP_CURL_INFO(STARTTRANSFER_TIME);
668 #if LIBCURL_VERSION_NUM >= 0x070907
669 HTTP_CURL_INFO(REDIRECT_TIME);
670 HTTP_CURL_INFO(REDIRECT_COUNT);
671 #endif
672 HTTP_CURL_INFO(SIZE_UPLOAD);
673 HTTP_CURL_INFO(SIZE_DOWNLOAD);
674 HTTP_CURL_INFO(SPEED_DOWNLOAD);
675 HTTP_CURL_INFO(SPEED_UPLOAD);
676 HTTP_CURL_INFO(HEADER_SIZE);
677 HTTP_CURL_INFO(REQUEST_SIZE);
678 HTTP_CURL_INFO(SSL_VERIFYRESULT);
679 #if LIBCURL_VERSION_NUM >= 0x070c03
680 /*HTTP_CURL_INFO(SSL_ENGINES); todo: CURLINFO_SLIST */
681 #endif
682 HTTP_CURL_INFO(CONTENT_LENGTH_DOWNLOAD);
683 HTTP_CURL_INFO(CONTENT_LENGTH_UPLOAD);
684 HTTP_CURL_INFO(CONTENT_TYPE);
685 #if LIBCURL_VERSION_NUM >= 0x070a03
686 /*HTTP_CURL_INFO(PRIVATE);*/
687 #endif
688 #if LIBCURL_VERSION_NUM >= 0x070a08
689 HTTP_CURL_INFO(HTTPAUTH_AVAIL);
690 HTTP_CURL_INFO(PROXYAUTH_AVAIL);
691 #endif
692 #if LIBCURL_VERSION_NUM >= 0x070c02
693 /*HTTP_CURL_INFO(OS_ERRNO);*/
694 #endif
695 #if LIBCURL_VERSION_NUM >= 0x070c03
696 HTTP_CURL_INFO(NUM_CONNECTS);
697 #endif
698 }
699 /* }}} */
700
701 /* {{{ STATUS http_request_ex(CURL *, http_request_method, char *, http_request_body, HashTable, HashTable, phpstr *) */
702 PHP_HTTP_API STATUS _http_request_ex(CURL *ch, http_request_method meth, const char *url, http_request_body *body, HashTable *options, HashTable *info, phpstr *response TSRMLS_DC)
703 {
704 if (SUCCESS != http_request_init(ch, meth, url, body, options, response)) {
705 return FAILURE;
706 }
707 return http_request_exec(ch, info);
708 }
709 /* }}} */
710
711 /* {{{ char *http_request_method_name(http_request_method) */
712 PHP_HTTP_API const char *_http_request_method_name(http_request_method m TSRMLS_DC)
713 {
714 zval **meth;
715
716 if (HTTP_STD_REQUEST_METHOD(m)) {
717 return http_request_methods[m];
718 }
719
720 if (SUCCESS == zend_hash_index_find(&HTTP_G(request).methods.custom, HTTP_CUSTOM_REQUEST_METHOD(m), (void **) &meth)) {
721 return Z_STRVAL_PP(meth);
722 }
723
724 return http_request_methods[0];
725 }
726 /* }}} */
727
728 /* {{{ unsigned long http_request_method_exists(zend_bool, unsigned long, char *) */
729 PHP_HTTP_API unsigned long _http_request_method_exists(zend_bool by_name, unsigned long id, const char *name TSRMLS_DC)
730 {
731 if (by_name) {
732 unsigned i;
733
734 for (i = HTTP_NO_REQUEST_METHOD + 1; i < HTTP_MAX_REQUEST_METHOD; ++i) {
735 if (!strcmp(name, http_request_methods[i])) {
736 return i;
737 }
738 }
739 {
740 zval **data;
741 char *key;
742 ulong idx;
743
744 FOREACH_HASH_KEYVAL(&HTTP_G(request).methods.custom, key, idx, data) {
745 if (!strcmp(name, Z_STRVAL_PP(data))) {
746 return idx + HTTP_MAX_REQUEST_METHOD;
747 }
748 }
749 }
750 return 0;
751 } else {
752 return HTTP_STD_REQUEST_METHOD(id) || zend_hash_index_exists(&HTTP_G(request).methods.custom, HTTP_CUSTOM_REQUEST_METHOD(id)) ? id : 0;
753 }
754 }
755 /* }}} */
756
757 /* {{{ unsigned long http_request_method_register(char *) */
758 PHP_HTTP_API unsigned long _http_request_method_register(const char *method TSRMLS_DC)
759 {
760 zval array;
761 char *http_method;
762 unsigned long meth_num = HTTP_G(request).methods.custom.nNextFreeElement + HTTP_MAX_REQUEST_METHOD;
763
764 Z_ARRVAL(array) = &HTTP_G(request).methods.custom;
765 add_next_index_string(&array, estrdup(method), 0);
766
767 spprintf(&http_method, 0, "HTTP_%s", method);
768 zend_register_long_constant(http_method, strlen(http_method) + 1, meth_num, CONST_CS, http_module_number TSRMLS_CC);
769 efree(http_method);
770
771 return meth_num;
772 }
773 /* }}} */
774
775 /* {{{ STATUS http_request_method_unregister(usngigned long) */
776 PHP_HTTP_API STATUS _http_request_method_unregister(unsigned long method TSRMLS_DC)
777 {
778 zval **zmethod;
779 char *http_method;
780
781 if (SUCCESS != zend_hash_index_find(&HTTP_G(request).methods.custom, HTTP_CUSTOM_REQUEST_METHOD(method), (void **) &zmethod)) {
782 http_error_ex(E_NOTICE, HTTP_E_PARAM, "Request method with id %lu does not exist", method);
783 return FAILURE;
784 }
785
786 spprintf(&http_method, 0, "HTTP_%s", Z_STRVAL_PP(zmethod));
787
788 if ( (SUCCESS != zend_hash_index_del(&HTTP_G(request).methods.custom, HTTP_CUSTOM_REQUEST_METHOD(method)))
789 || (SUCCESS != zend_hash_del(EG(zend_constants), http_method, strlen(http_method) + 1))) {
790 http_error_ex(E_NOTICE, 0, "Could not unregister request method: %s", http_method);
791 efree(http_method);
792 return FAILURE;
793 }
794
795 efree(http_method);
796 return SUCCESS;
797 }
798 /* }}} */
799
800
801 /* {{{ http_request_pool *http_request_pool_init(http_request_pool *) */
802 PHP_HTTP_API http_request_pool *_http_request_pool_init(http_request_pool *pool TSRMLS_DC)
803 {
804 zend_bool free_pool;
805
806 if ((free_pool = (!pool))) {
807 pool = emalloc(sizeof(http_request_pool));
808 pool->ch = NULL;
809 }
810
811 if (!pool->ch) {
812 if (!(pool->ch = curl_multi_init())) {
813 http_error(E_WARNING, HTTP_E_CURL, "Could not initialize curl");
814 if (free_pool) {
815 efree(pool);
816 }
817 return NULL;
818 }
819 }
820
821 pool->unfinished = 0;
822 zend_llist_init(&pool->handles, sizeof(zval *), (llist_dtor_func_t) ZVAL_PTR_DTOR, 0);
823 zend_llist_init(&pool->bodies, sizeof(http_request_body *), (llist_dtor_func_t) http_request_pool_freebody, 0);
824
825 return pool;
826 }
827 /* }}} */
828
829 /* {{{ STATUS http_request_pool_attach(http_request_pool *, zval *) */
830 PHP_HTTP_API STATUS _http_request_pool_attach(http_request_pool *pool, zval *request TSRMLS_DC)
831 {
832 getObjectEx(http_request_object, req, request);
833
834 if (req->attached) {
835 http_error(E_WARNING, HTTP_E_CURL, "HttpRequest object is already member of an HttpRequestPool");
836 } else {
837 CURLMcode code;
838 http_request_body *body = http_request_body_new();
839 zval *info = GET_PROP_EX(req, request, responseInfo);
840
841 if (SUCCESS != http_request_object_requesthandler(req, request, body)) {
842 efree(body);
843 http_error_ex(E_WARNING, HTTP_E_CURL, "Could not initialize HttpRequest object for attaching to the HttpRequestPool");
844 } else if (CURLM_OK != (code = curl_multi_add_handle(pool->ch, req->ch))) {
845 http_error_ex(E_WARNING, HTTP_E_CURL, "Could not attach HttpRequest object to the HttpRequestPool: %s", curl_multi_strerror(code));
846 } else {
847 req->attached = 1;
848 zval_add_ref(&request);
849 zend_llist_add_element(&pool->handles, &request);
850 zend_llist_add_element(&pool->bodies, &body);
851 return SUCCESS;
852 }
853 }
854 return FAILURE;
855 }
856 /* }}} */
857
858 /* {{{ STATUS http_request_pool_detach(http_request_pool *, zval *) */
859 PHP_HTTP_API STATUS _http_request_pool_detach(http_request_pool *pool, zval *request TSRMLS_DC)
860 {
861 getObjectEx(http_request_object, req, request);
862
863 if (!req->attached) {
864 http_error(E_WARNING, HTTP_E_CURL, "HttpRequest object is not attached to an HttpRequestPool");
865 } else {
866 CURLMcode code;
867
868 if (CURLM_OK != (code = curl_multi_remove_handle(pool->ch, req->ch))) {
869 http_error_ex(E_WARNING, HTTP_E_CURL, "Could not detach HttpRequest object from the HttpRequestPool: %s", curl_multi_strerror(code));
870 } else {
871 req->attached = 0;
872 zval_ptr_dtor(&request);
873 return SUCCESS;
874 }
875 }
876 return FAILURE;
877 }
878 /* }}} */
879
880 /* {{{ STATUS http_request_pool_send(http_request_pool *) */
881 PHP_HTTP_API STATUS _http_request_pool_send(http_request_pool *pool TSRMLS_DC)
882 {
883 http_request_pool_perform(pool);
884 while (pool->unfinished) {
885 if (SUCCESS != http_request_pool_select(pool)) {
886 http_error(E_WARNING, HTTP_E_CURL, "Socket error");
887 return FAILURE;
888 }
889 http_request_pool_perform(pool);
890 }
891 zend_llist_apply(&pool->handles, (llist_apply_func_t) http_request_pool_responsehandler TSRMLS_CC);
892 return SUCCESS;
893 }
894 /* }}} */
895
896 /*#*/
897
898 /* {{{ static void http_request_pool_free_body(http_request_body *) */
899 static void http_request_pool_freebody(http_request_body **body)
900 {
901 TSRMLS_FETCH();
902 http_request_body_free(*body);
903 }
904 /* }}} */
905
906 /* {{{ static void http_request_pool_responsehandler(zval **) */
907 static void http_request_pool_responsehandler(zval **req TSRMLS_DC)
908 {
909 getObjectEx(http_request_object, obj, *req);
910 http_request_object_responsehandler(obj, *req, NULL);
911 }
912 /* }}} */
913
914 /* {{{ static inline STATUS http_request_pool_select(http_request_pool *) */
915 static inline STATUS http_request_pool_select(http_request_pool *pool)
916 {
917 int MAX;
918 fd_set R, W, E;
919 struct timeval timeout = {1, 0};
920
921 FD_ZERO(&R);
922 FD_ZERO(&W);
923 FD_ZERO(&E);
924
925 curl_multi_fdset(pool->ch, &R, &W, &E, &MAX);
926 return (-1 != select(MAX + 1, &R, &W, &E, &timeout)) ? SUCCESS : FAILURE;
927 }
928 /* }}} */
929
930 /* {{{ static inline void http_request_pool_perform(http_request_pool *) */
931 static inline void http_request_pool_perform(http_request_pool *pool)
932 {
933 while (CURLM_CALL_MULTI_PERFORM == curl_multi_perform(pool->ch, &pool->unfinished));
934 }
935 /* }}} */
936
937 /* {{{ char *http_request_methods[] */
938 static const char *const http_request_methods[] = {
939 "UNKOWN",
940 /* HTTP/1.1 */
941 "GET",
942 "HEAD",
943 "POST",
944 "PUT",
945 "DELETE",
946 "OPTIONS",
947 "TRACE",
948 "CONNECT",
949 /* WebDAV - RFC 2518 */
950 "PROPFIND",
951 "PROPPATCH",
952 "MKCOL",
953 "COPY",
954 "MOVE",
955 "LOCK",
956 "UNLOCK",
957 /* WebDAV Versioning - RFC 3253 */
958 "VERSION-CONTROL",
959 "REPORT",
960 "CHECKOUT",
961 "CHECKIN",
962 "UNCHECKOUT",
963 "MKWORKSPACE",
964 "UPDATE",
965 "LABEL",
966 "MERGE",
967 "BASELINE-CONTROL",
968 "MKACTIVITY",
969 /* WebDAV Access Control - RFC 3744 */
970 "ACL",
971 NULL
972 };
973 /* }}} */
974
975 /* {{{ static size_t http_curl_write_callback(char *, size_t, size_t, void *) */
976 static size_t http_curl_write_callback(char *buf, size_t len, size_t n, void *s)
977 {
978 HTTP_CURL_CALLBACK_DATA(s, phpstr *, str);
979 return str ? phpstr_append(PHPSTR(str), buf, len * n) : len * n;
980 }
981 /* }}} */
982
983 /* {{{ static size_t http_curl_read_callback(void *, size_t, size_t, void *) */
984 static size_t http_curl_read_callback(void *data, size_t len, size_t n, void *s)
985 {
986 static char *offset = NULL, *original = NULL;
987 HTTP_CURL_CALLBACK_DATA(s, http_request_body *, body);
988
989 switch (body->type)
990 {
991 case HTTP_REQUEST_BODY_UPLOADFILE:
992 {
993 TSRMLS_FETCH();
994 return php_stream_read((php_stream *) body->data, data, len * n);
995 }
996 break;
997
998 case HTTP_REQUEST_BODY_UPLOADDATA:
999 {
1000 size_t avail;
1001 if (original != s) {
1002 original = offset = s;
1003 }
1004 if ((avail = body->size - (offset - original)) < 1) {
1005 return 0;
1006 }
1007 if (avail < (len * n)) {
1008 memcpy(data, offset, avail);
1009 offset += avail;
1010 return avail;
1011 } else {
1012 memcpy(data, offset, len * n);
1013 offset += len * n;
1014 return len * n;
1015 }
1016 }
1017 break;
1018
1019 default:
1020 return 0;
1021 break;
1022 }
1023 }
1024 /* }}} */
1025
1026 /* {{{ http_curl_callback_ctx http_curl_callback_data(void *) */
1027 static http_curl_callback_ctx *_http_curl_callback_data(void *data TSRMLS_DC)
1028 {
1029 http_curl_callback_ctx *ctx = emalloc(sizeof(http_curl_callback_ctx));
1030 TSRMLS_SET_CTX(ctx->tsrm_ctx);
1031 ctx->data = data;
1032 return ctx;
1033 }
1034 /* }}} */
1035
1036 /* {{{ static int http_curl_progress_callback(void *, double, double, double, double) */
1037 static int http_curl_progress_callback(void *data, double dltotal, double dlnow, double ultotal, double ulnow)
1038 {
1039 zval *params_pass[4], params_local[4], retval;
1040 HTTP_CURL_CALLBACK_DATA(data, zval *, func);
1041
1042 params_pass[0] = &params_local[0];
1043 params_pass[1] = &params_local[1];
1044 params_pass[2] = &params_local[2];
1045 params_pass[3] = &params_local[3];
1046
1047 ZVAL_DOUBLE(params_pass[0], dltotal);
1048 ZVAL_DOUBLE(params_pass[1], dlnow);
1049 ZVAL_DOUBLE(params_pass[2], ultotal);
1050 ZVAL_DOUBLE(params_pass[3], ulnow);
1051
1052 return call_user_function(EG(function_table), NULL, func, &retval, 4, params_pass TSRMLS_CC);
1053 }
1054 /* }}} */
1055
1056 static int http_curl_debug_callback(CURL *ch, curl_infotype type, char *string, size_t length, void *data)
1057 {
1058 zval *params_pass[2], params_local[2], retval;
1059 HTTP_CURL_CALLBACK_DATA(data, zval *, func);
1060
1061 params_pass[0] = &params_local[0];
1062 params_pass[1] = &params_local[1];
1063
1064 ZVAL_LONG(params_pass[0], type);
1065 ZVAL_STRINGL(params_pass[1], string, length, 1);
1066
1067 call_user_function(EG(function_table), NULL, func, &retval, 2, params_pass TSRMLS_CC);
1068
1069 return 0;
1070 }
1071 /* {{{ static inline zval *http_curl_getopt(HashTable *, char *, size_t, int) */
1072 static inline zval *_http_curl_getopt_ex(HashTable *options, char *key, size_t keylen, int type TSRMLS_DC)
1073 {
1074 zval **zoption;
1075
1076 if (!options || (SUCCESS != zend_hash_find(options, key, keylen, (void **) &zoption))) {
1077 return NULL;
1078 }
1079
1080 if (Z_TYPE_PP(zoption) != type) {
1081 switch (type)
1082 {
1083 case IS_BOOL: convert_to_boolean_ex(zoption); break;
1084 case IS_LONG: convert_to_long_ex(zoption); break;
1085 case IS_DOUBLE: convert_to_double_ex(zoption); break;
1086 case IS_STRING: convert_to_string_ex(zoption); break;
1087 case IS_ARRAY: convert_to_array_ex(zoption); break;
1088 case IS_OBJECT: convert_to_object_ex(zoption); break;
1089 default:
1090 break;
1091 }
1092 }
1093
1094 return *zoption;
1095 }
1096 /* }}} */
1097
1098 /*
1099 * Local variables:
1100 * tab-width: 4
1101 * c-basic-offset: 4
1102 * End:
1103 * vim600: noet sw=4 ts=4 fdm=marker
1104 * vim<600: noet sw=4 ts=4
1105 */
1106