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