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