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