header cleanups; fix IDE warnings
[m6w6/ext-http] / php_http_neon.c
1 /*
2 +--------------------------------------------------------------------+
3 | PECL :: http |
4 +--------------------------------------------------------------------+
5 | Redistribution and use in source and binary forms, with or without |
6 | modification, are permitted provided that the conditions mentioned |
7 | in the accompanying LICENSE file are met. |
8 +--------------------------------------------------------------------+
9 | Copyright (c) 2004-2011, Michael Wallner <mike@php.net> |
10 +--------------------------------------------------------------------+
11 */
12
13 #include "php_http_api.h"
14
15 #if PHP_HTTP_HAVE_NEON
16
17 #include "php_http_request.h"
18
19 #include <neon/ne_auth.h>
20 #include <neon/ne_compress.h>
21 #include <neon/ne_session.h>
22 #include <neon/ne_request.h>
23 #include <neon/ne_redirect.h>
24
25 typedef struct php_http_neon_auth {
26 long type;
27 char *user;
28 char *pass;
29 } php_http_neon_auth_t;
30
31 typedef struct php_http_neon_request {
32 php_http_message_body_t *body;
33
34 struct {
35 HashTable cache;
36
37 php_http_buffer_t headers;
38 char *useragent;
39 char *referer;
40 char *url;
41 short port;
42
43 struct {
44 int type;
45 short port;
46 char *host;
47 } proxy;
48
49 struct {
50 php_http_neon_auth_t proxy;
51 php_http_neon_auth_t http;
52 } auth;
53
54 struct {
55 unsigned noverify:1;
56 ne_ssl_client_cert *clicert;
57 ne_ssl_certificate *trucert;
58 } ssl;
59
60 long redirects;
61 char *cookiestore;
62 ne_inet_addr *interface;
63 long maxfilesize;
64
65 struct {
66 unsigned count;
67 double delay;
68 } retry;
69
70 struct {
71 long connect;
72 long read;
73 } timeout;
74 } options;
75
76 php_http_request_progress_t progress;
77
78 } php_http_neon_request_t;
79
80 /* callbacks */
81
82 static ssize_t php_http_neon_read_callback(void *ctx, char *buf, size_t len)
83 {
84 php_http_request_t *h = ctx;
85 php_http_neon_request_t *neon = h->ctx;
86 php_http_message_body_t *body = neon->body;
87
88 if (body) {
89 TSRMLS_FETCH_FROM_CTX(body->ts);
90
91 if (buf) {
92 size_t read = php_stream_read(php_http_message_body_stream(body), buf, len);
93
94 php_http_buffer_append(h->buffer, buf, read);
95 php_http_message_parser_parse(h->parser, h->buffer, 0, &h->message);
96 return read;
97 } else {
98 return php_stream_rewind(php_http_message_body_stream(body));
99 }
100 }
101 return 0;
102 }
103
104 static void php_http_neon_pre_send_callback(ne_request *req, void *ctx, ne_buffer *header)
105 {
106 php_http_request_t *h = ctx;
107 php_http_neon_request_t *neon = h->ctx;
108
109 ne_buffer_append(header, neon->options.headers.data, neon->options.headers.used);
110
111 php_http_buffer_append(h->buffer, header->data, header->used - 1 /* ne_buffer counts \0 */);
112 php_http_buffer_appends(h->buffer, PHP_HTTP_CRLF);
113 php_http_message_parser_parse(h->parser, h->buffer, 0, &h->message);
114 }
115
116 static void php_http_neon_post_headers_callback(ne_request *req, void *ctx, const ne_status *status)
117 {
118 php_http_request_t *h = ctx;
119 HashTable *hdrs = &h->message->hdrs;
120 php_http_info_t i;
121 void *iter = NULL;
122 zval tmp;
123 const char *name, *value;
124 TSRMLS_FETCH_FROM_CTX(h->ts);
125
126 php_http_info_init(&i TSRMLS_CC);
127 i.type = PHP_HTTP_RESPONSE;
128 php_http_version_init(&i.http.version, status->major_version, status->minor_version TSRMLS_CC);
129 i.http.info.response.code = status->code;
130 i.http.info.response.status = estrdup(status->reason_phrase);
131 php_http_message_info_callback(&h->message, &hdrs, &i TSRMLS_CC);
132 php_http_info_dtor(&i);
133
134 INIT_PZVAL_ARRAY(&tmp, hdrs);
135 while ((iter = ne_response_header_iterate(req, iter, &name, &value))) {
136 char *key = php_http_pretty_key(estrdup(name), strlen(name), 1, 1);
137 add_assoc_string(&tmp, key, estrdup(value), 0);
138 efree(key);
139 }
140 php_http_message_parser_state_push(h->parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_HEADER_DONE);
141 }
142
143 static int php_http_neon_ssl_verify_callback(void *ctx, int failures, const ne_ssl_certificate *cert)
144 {
145 php_http_request_t *h = ctx;
146 php_http_neon_request_t *neon = h->ctx;
147
148 if (neon->options.ssl.noverify) {
149 return 0;
150 }
151 return failures;
152 }
153
154 static void php_http_neon_progress_callback(void *ctx, ne_session_status status, const ne_session_status_info *info)
155 {
156 php_http_request_t *h = ctx;
157 php_http_neon_request_t *neon = h->ctx;
158 TSRMLS_FETCH_FROM_CTX(h->ts);
159
160 switch (status) {
161 case ne_status_lookup:
162 neon->progress.state.info = "resolve";
163 break;
164 case ne_status_connecting:
165 neon->progress.state.info = "connect";
166 break;
167 case ne_status_connected:
168 neon->progress.state.info = "connected";
169 break;
170 case ne_status_sending:
171 neon->progress.state.info = "send";
172 neon->progress.state.ul.total = info->sr.total;
173 neon->progress.state.ul.now = info->sr.progress;
174 break;
175 case ne_status_recving:
176 neon->progress.state.info = "receive";
177 neon->progress.state.dl.total = info->sr.total;
178 neon->progress.state.dl.now = info->sr.progress;
179 break;
180 case ne_status_disconnected:
181 neon->progress.state.info = "disconnected";
182 break;
183 }
184
185 php_http_request_progress_notify(&neon->progress TSRMLS_CC);
186 }
187
188 /* helpers */
189
190 static inline zval *cache_option(HashTable *cache, char *key, size_t keylen, ulong h, zval *opt)
191 {
192 Z_ADDREF_P(opt);
193
194 if (h) {
195 zend_hash_quick_update(cache, key, keylen, h, &opt, sizeof(zval *), NULL);
196 } else {
197 zend_hash_update(cache, key, keylen, &opt, sizeof(zval *), NULL);
198 }
199
200 return opt;
201 }
202
203 static inline zval *get_option(HashTable *cache, HashTable *options, char *key, size_t keylen, int type)
204 {
205 if (options) {
206 zval **zoption;
207 ulong h = zend_hash_func(key, keylen);
208
209 if (SUCCESS == zend_hash_quick_find(options, key, keylen, h, (void *) &zoption)) {
210 zval *option = php_http_ztyp(type, *zoption);
211
212 if (cache) {
213 zval *cached = cache_option(cache, key, keylen, h, option);
214
215 zval_ptr_dtor(&option);
216 return cached;
217 }
218 return option;
219 }
220 }
221
222 return NULL;
223 }
224
225 static STATUS set_options(php_http_request_t *h, HashTable *options)
226 {
227 zval *zoption;
228 int range_req = 0;
229 php_http_neon_request_t *neon = h->ctx;
230 TSRMLS_FETCH_FROM_CTX(h->ts);
231
232 /* proxy */
233 if ((zoption = get_option(&neon->options.cache, options, ZEND_STRS("proxyhost"), IS_STRING))) {
234 neon->options.proxy.host = Z_STRVAL_P(zoption);
235
236 /* user:pass */
237 if ((zoption = get_option(&neon->options.cache, options, ZEND_STRS("proxyauth"), IS_STRING)) && Z_STRLEN_P(zoption)) {
238 char *colon = strchr(Z_STRVAL_P(zoption), ':');
239
240 if (colon) {
241 STR_SET(neon->options.auth.proxy.user, estrndup(Z_STRVAL_P(zoption), colon - Z_STRVAL_P(zoption)));
242 STR_SET(neon->options.auth.proxy.pass, estrdup(colon + 1));
243 } else {
244 STR_SET(neon->options.auth.proxy.user, estrdup(Z_STRVAL_P(zoption)));
245 }
246 }
247
248 if ((zoption = get_option(&neon->options.cache, options, ZEND_STRS("proxyauthtype"), IS_LONG))) {
249 neon->options.auth.proxy.type = Z_LVAL_P(zoption);
250 } else {
251 neon->options.auth.proxy.type = NE_AUTH_ALL;
252 }
253
254
255 /* port */
256 if ((zoption = get_option(&neon->options.cache, options, ZEND_STRS("proxyport"), IS_LONG))) {
257 neon->options.proxy.port = Z_LVAL_P(zoption);
258 } else {
259 neon->options.proxy.port = 0;
260 }
261
262 /* type */
263 if ((zoption = get_option(&neon->options.cache, options, ZEND_STRS("proxytype"), IS_LONG))) {
264 neon->options.proxy.type = Z_LVAL_P(zoption);
265 } else {
266 neon->options.proxy.type = -1;
267 }
268 }
269
270 /* outgoing interface */
271 if ((zoption = get_option(&neon->options.cache, options, ZEND_STRS("interface"), IS_STRING))) {
272 if (!(neon->options.interface = ne_iaddr_parse(Z_STRVAL_P(zoption), ne_iaddr_ipv4))) {
273 neon->options.interface = ne_iaddr_parse(Z_STRVAL_P(zoption), ne_iaddr_ipv6);
274 }
275 }
276
277 /* another port */
278 if ((zoption = get_option(&neon->options.cache, options, ZEND_STRS("port"), IS_LONG))) {
279 neon->options.port = Z_LVAL_P(zoption);
280 }
281
282 /* auth */
283 if ((zoption = get_option(&neon->options.cache, options, ZEND_STRS("httpauth"), IS_STRING)) && Z_STRLEN_P(zoption)) {
284 char *colon = strchr(Z_STRVAL_P(zoption), ':');
285
286 if (colon) {
287 STR_SET(neon->options.auth.http.user, estrndup(Z_STRVAL_P(zoption), colon - Z_STRVAL_P(zoption)));
288 STR_SET(neon->options.auth.http.pass, estrdup(colon + 1));
289 } else {
290 STR_SET(neon->options.auth.http.user, estrdup(Z_STRVAL_P(zoption)));
291 }
292 }
293 if ((zoption = get_option(&neon->options.cache, options, ZEND_STRS("httpauthtype"), IS_LONG))) {
294 neon->options.auth.http.type = Z_LVAL_P(zoption);
295 } else {
296 neon->options.auth.http.type = NE_AUTH_ALL;
297 }
298
299 /* redirects, defaults to 0 */
300 if ((zoption = get_option(&neon->options.cache, options, ZEND_STRS("redirect"), IS_LONG))) {
301 neon->options.redirects = Z_LVAL_P(zoption);
302 } else {
303 neon->options.redirects = 0;
304 }
305
306 /* retries, defaults to 0 */
307 if ((zoption = get_option(&neon->options.cache, options, ZEND_STRS("retrycount"), IS_LONG))) {
308 neon->options.retry.count = Z_LVAL_P(zoption);
309 if ((zoption = get_option(&neon->options.cache, options, ZEND_STRS("retrydelay"), IS_DOUBLE))) {
310 neon->options.retry.delay = Z_DVAL_P(zoption);
311 } else {
312 neon->options.retry.delay = 0;
313 }
314 } else {
315 neon->options.retry.count = 0;
316 }
317
318 /* referer */
319 if ((zoption = get_option(&neon->options.cache, options, ZEND_STRS("referer"), IS_STRING)) && Z_STRLEN_P(zoption)) {
320 neon->options.referer = Z_STRVAL_P(zoption);
321 }
322
323 /* useragent, default "PECL::HTTP/version (PHP/version)" */
324 if ((zoption = get_option(&neon->options.cache, options, ZEND_STRS("useragent"), IS_STRING))) {
325 /* allow to send no user agent, not even default one */
326 if (Z_STRLEN_P(zoption)) {
327 neon->options.useragent = Z_STRVAL_P(zoption);
328 } else {
329 neon->options.useragent = NULL;
330 }
331 }
332
333 /* resume */
334 if ((zoption = get_option(&neon->options.cache, options, ZEND_STRS("resume"), IS_LONG)) && (Z_LVAL_P(zoption) > 0)) {
335 php_http_buffer_appendf(&neon->options.headers, "Range: bytes=%ld-" PHP_HTTP_CRLF, Z_LVAL_P(zoption));
336 range_req = 1;
337 }
338 /* or range of kind array(array(0,499), array(100,1499)) */
339 else if ((zoption = get_option(&neon->options.cache, options, ZEND_STRS("range"), IS_ARRAY)) && zend_hash_num_elements(Z_ARRVAL_P(zoption))) {
340 HashPosition pos1, pos2;
341 zval **rr, **rb, **re;
342 php_http_buffer_t rs;
343
344 php_http_buffer_init(&rs);
345 FOREACH_VAL(pos1, zoption, rr) {
346 if (Z_TYPE_PP(rr) == IS_ARRAY) {
347 zend_hash_internal_pointer_reset_ex(Z_ARRVAL_PP(rr), &pos2);
348 if (SUCCESS == zend_hash_get_current_data_ex(Z_ARRVAL_PP(rr), (void *) &rb, &pos2)) {
349 zend_hash_move_forward_ex(Z_ARRVAL_PP(rr), &pos2);
350 if (SUCCESS == zend_hash_get_current_data_ex(Z_ARRVAL_PP(rr), (void *) &re, &pos2)) {
351 if ( ((Z_TYPE_PP(rb) == IS_LONG) || ((Z_TYPE_PP(rb) == IS_STRING) && is_numeric_string(Z_STRVAL_PP(rb), Z_STRLEN_PP(rb), NULL, NULL, 1))) &&
352 ((Z_TYPE_PP(re) == IS_LONG) || ((Z_TYPE_PP(re) == IS_STRING) && is_numeric_string(Z_STRVAL_PP(re), Z_STRLEN_PP(re), NULL, NULL, 1)))) {
353 zval *rbl = php_http_ztyp(IS_LONG, *rb);
354 zval *rel = php_http_ztyp(IS_LONG, *re);
355
356 if ((Z_LVAL_P(rbl) >= 0) && (Z_LVAL_P(rel) >= 0)) {
357 php_http_buffer_appendf(&rs, "%ld-%ld,", Z_LVAL_P(rbl), Z_LVAL_P(rel));
358 }
359 zval_ptr_dtor(&rbl);
360 zval_ptr_dtor(&rel);
361 }
362 }
363 }
364 }
365 }
366
367 if (PHP_HTTP_BUFFER_LEN(&rs)) {
368 /* ignore last comma */
369 int used = rs.used > INT_MAX ? INT_MAX : rs.used;
370 php_http_buffer_appendf(&neon->options.headers, "Range: bytes=%.*s" PHP_HTTP_CRLF, used - 1, rs.data);
371 range_req = 1;
372 }
373 php_http_buffer_dtor(&rs);
374 }
375
376 /* additional headers, array('name' => 'value') */
377 if ((zoption = get_option(&neon->options.cache, options, ZEND_STRS("headers"), IS_ARRAY))) {
378 php_http_array_hashkey_t header_key = php_http_array_hashkey_init(0);
379 zval **header_val;
380 HashPosition pos;
381
382 FOREACH_KEYVAL(pos, zoption, header_key, header_val) {
383 if (header_key.type == HASH_KEY_IS_STRING) {
384 zval *header_cpy = php_http_ztyp(IS_STRING, *header_val);
385
386 if (!strcasecmp(header_key.str, "range")) {
387 range_req = 1;
388 }
389 php_http_buffer_appendf(&neon->options.headers, "%s: %s" PHP_HTTP_CRLF, header_key.str, Z_STRVAL_P(header_cpy));
390 zval_ptr_dtor(&header_cpy);
391 }
392 }
393 }
394 /* etag */
395 if ((zoption = get_option(&neon->options.cache, options, ZEND_STRS("etag"), IS_STRING)) && Z_STRLEN_P(zoption)) {
396 php_http_buffer_appends(&neon->options.headers, "If-");
397 if (range_req) {
398 php_http_buffer_appends(&neon->options.headers, "None-");
399 }
400 php_http_buffer_appends(&neon->options.headers, "Match: ");
401
402 if ((Z_STRVAL_P(zoption)[0] == '"') && (Z_STRVAL_P(zoption)[Z_STRLEN_P(zoption)-1] == '"')) {
403 php_http_buffer_appendl(&neon->options.headers, Z_STRVAL_P(zoption));
404 } else {
405 php_http_buffer_appendf(&neon->options.headers, "\"%s\"" PHP_HTTP_CRLF, Z_STRVAL_P(zoption));
406 }
407 }
408 /* compression */
409 if ((zoption = get_option(&neon->options.cache, options, ZEND_STRS("compress"), IS_BOOL)) && Z_LVAL_P(zoption)) {
410 php_http_buffer_appends(&neon->options.headers, "Accept-Encoding: gzip;q=1.0,deflate;q=0.5" PHP_HTTP_CRLF);
411 }
412
413 /* lastmodified */
414 if ((zoption = get_option(&neon->options.cache, options, ZEND_STRS("lastmodified"), IS_LONG))) {
415 if (Z_LVAL_P(zoption)) {
416 time_t time = Z_LVAL_P(zoption) > 0 ? Z_LVAL_P(zoption) : PHP_HTTP_G->env.request.time + Z_LVAL_P(zoption);
417 char *date = php_format_date(ZEND_STRS(PHP_HTTP_DATE_FORMAT), time, 0 TSRMLS_CC);
418
419 php_http_buffer_appends(&neon->options.headers, "If-");
420 if (range_req) {
421 php_http_buffer_appends(&neon->options.headers, "Un");
422 }
423 php_http_buffer_appendf(&neon->options.headers, "Modified-Since: %s" PHP_HTTP_CRLF, date);
424 efree(date);
425 }
426 }
427
428 /* cookies, array('name' => 'value') */
429 if ((zoption = get_option(&neon->options.cache, options, ZEND_STRS("cookies"), IS_ARRAY))) {
430 php_http_buffer_t cookies;
431
432 php_http_buffer_init(&cookies);
433 if (zend_hash_num_elements(Z_ARRVAL_P(zoption))) {
434 zval *urlenc_cookies = NULL;
435
436 php_http_buffer_appends(&neon->options.headers, "Cookie: ");
437
438 /* check whether cookies should not be urlencoded; default is to urlencode them */
439 if ((!(urlenc_cookies = get_option(&neon->options.cache, options, ZEND_STRS("encodecookies"), IS_BOOL))) || Z_BVAL_P(urlenc_cookies)) {
440 php_http_url_encode_hash_ex(HASH_OF(zoption), &neon->options.headers, ZEND_STRS("; "), ZEND_STRS("="), NULL, 0 TSRMLS_CC);
441 } else {
442 HashPosition pos;
443 php_http_array_hashkey_t cookie_key = php_http_array_hashkey_init(0);
444 zval **cookie_val;
445
446 FOREACH_KEYVAL(pos, zoption, cookie_key, cookie_val) {
447 if (cookie_key.type == HASH_KEY_IS_STRING) {
448 zval *val = php_http_ztyp(IS_STRING, *cookie_val);
449 php_http_buffer_appendf(&neon->options.headers, "%s=%s; ", cookie_key.str, Z_STRVAL_P(val));
450 zval_ptr_dtor(&val);
451 }
452 }
453 }
454 }
455 neon->options.headers.used -= lenof("; ");
456 php_http_buffer_appends(&neon->options.headers, PHP_HTTP_CRLF);
457 }
458 /* cookiestore, read initial cookies from that file and store cookies back into that file */
459 if ((zoption = get_option(&neon->options.cache, options, ZEND_STRS("cookiestore"), IS_STRING))) {
460 neon->options.cookiestore = Z_STRVAL_P(zoption);
461 }
462
463 /* maxfilesize */
464 if ((zoption = get_option(&neon->options.cache, options, ZEND_STRS("maxfilesize"), IS_LONG))) {
465 neon->options.maxfilesize = Z_LVAL_P(zoption);
466 }
467
468 /* READ timeout */
469 if ((zoption = get_option(&neon->options.cache, options, ZEND_STRS("timeout"), IS_DOUBLE))) {
470 neon->options.timeout.read = Z_DVAL_P(zoption) > 0 && Z_DVAL_P(zoption) < 1 ? 1 : round(Z_DVAL_P(zoption));
471 }
472 /* connecttimeout, defaults to 0 */
473 if ((zoption = get_option(&neon->options.cache, options, ZEND_STRS("connecttimeout"), IS_DOUBLE))) {
474 neon->options.timeout.connect = Z_DVAL_P(zoption) > 0 && Z_DVAL_P(zoption) < 1 ? 1 : round(Z_DVAL_P(zoption));
475 }
476
477 /* ssl */
478 if ((zoption = get_option(&neon->options.cache, options, ZEND_STRS("ssl"), IS_ARRAY))) {
479 zval **zssl;
480
481 if (SUCCESS == zend_hash_find(Z_ARRVAL_P(zoption), ZEND_STRS("verifypeer"), (void *) &zssl)) {
482 if (!i_zend_is_true(*zssl)) {
483 neon->options.ssl.noverify = 1;
484 }
485 }
486 if (SUCCESS == zend_hash_find(Z_ARRVAL_P(zoption), ZEND_STRS("key"), (void *) &zssl)) {
487 zval *cpy = php_http_ztyp(IS_STRING, *zssl);
488 ne_ssl_client_cert *cc = ne_ssl_clicert_read(Z_STRVAL_P(cpy));
489
490 if (cc) {
491 if (ne_ssl_clicert_encrypted(cc)) {
492 if (SUCCESS == zend_hash_find(Z_ARRVAL_P(zoption), ZEND_STRS("keypasswd"), (void *) &zssl)) {
493 zval *cpy = php_http_ztyp(IS_STRING, *zssl);
494
495 if (NE_OK == ne_ssl_clicert_decrypt(cc, Z_STRVAL_P(cpy))) {
496 neon->options.ssl.clicert = cc;
497 }
498 zval_ptr_dtor(&cpy);
499 }
500 }
501 }
502
503 if (cc && !neon->options.ssl.clicert) {
504 ne_ssl_clicert_free(cc);
505 }
506
507 zval_ptr_dtor(&cpy);
508 }
509 if (SUCCESS == zend_hash_find(Z_ARRVAL_P(zoption), ZEND_STRS("cert"), (void *) &zssl)) {
510 zval *cpy = php_http_ztyp(IS_STRING, *zssl);
511 ne_ssl_certificate *tc = ne_ssl_cert_read(Z_STRVAL_P(cpy));
512
513 if (tc) {
514 neon->options.ssl.trucert = tc;
515 }
516 zval_ptr_dtor(&cpy);
517 }
518 }
519
520 return SUCCESS;
521 }
522
523 /* request handler ops */
524
525 static STATUS php_http_neon_request_reset(php_http_request_t *h);
526
527 static php_http_request_t *php_http_neon_request_init(php_http_request_t *h, void *dummy)
528 {
529 php_http_neon_request_t *ctx;
530
531 ctx = ecalloc(1, sizeof(*ctx));
532 php_http_buffer_init(&ctx->options.headers);
533 zend_hash_init(&ctx->options.cache, 0, NULL, ZVAL_PTR_DTOR, 0);
534 h->ctx = ctx;
535
536 return h;
537 }
538
539 static php_http_request_t *php_http_neon_request_copy(php_http_request_t *from, php_http_request_t *to)
540 {
541 TSRMLS_FETCH_FROM_CTX(from->ts);
542
543 if (to) {
544 return php_http_neon_request_init(to, NULL);
545 } else {
546 return php_http_request_init(NULL, from->ops, from->rf, NULL TSRMLS_CC);
547 }
548 }
549
550 static void php_http_neon_request_dtor(php_http_request_t *h)
551 {
552 php_http_neon_request_t *ctx = h->ctx;
553 TSRMLS_FETCH_FROM_CTX(h->ts);
554
555 php_http_neon_request_reset(h);
556 php_http_buffer_dtor(&ctx->options.headers);
557 zend_hash_destroy(&ctx->options.cache);
558
559 php_http_request_progress_dtor(&ctx->progress TSRMLS_CC);
560
561 efree(ctx);
562 h->ctx = NULL;
563 }
564
565 static STATUS php_http_neon_request_reset(php_http_request_t *h)
566 {
567 php_http_neon_request_t *neon = h->ctx;
568 TSRMLS_FETCH_FROM_CTX(h->ts);
569
570 php_http_buffer_reset(&neon->options.headers);
571 STR_SET(neon->options.useragent, NULL);
572 STR_SET(neon->options.url, NULL);
573 neon->options.port = 0;
574 neon->options.proxy.type = -1;
575 neon->options.proxy.port = 0;
576 STR_SET(neon->options.proxy.host, NULL);
577 neon->options.auth.proxy.type = 0;
578 STR_SET(neon->options.auth.proxy.user, NULL);
579 STR_SET(neon->options.auth.proxy.pass, NULL);
580 neon->options.auth.http.type = 0;
581 STR_SET(neon->options.auth.http.user, NULL);
582 STR_SET(neon->options.auth.http.pass, NULL);
583 neon->options.ssl.noverify = 0;
584 if (neon->options.ssl.clicert) {
585 ne_ssl_clicert_free(neon->options.ssl.clicert);
586 neon->options.ssl.clicert = NULL;
587 }
588 if (neon->options.ssl.trucert) {
589 ne_ssl_cert_free(neon->options.ssl.trucert);
590 neon->options.ssl.trucert = NULL;
591 }
592 neon->options.redirects = 0;
593 STR_SET(neon->options.cookiestore, NULL);
594 if (neon->options.interface) {
595 ne_iaddr_free(neon->options.interface);
596 neon->options.interface = NULL;
597 }
598 neon->options.maxfilesize = 0;
599 neon->options.retry.delay = 0;
600 neon->options.retry.count = 0;
601 neon->options.timeout.read = 0;
602 neon->options.timeout.connect = 0;
603
604 php_http_request_progress_dtor(&neon->progress TSRMLS_CC);
605
606 return SUCCESS;
607 }
608
609 static STATUS php_http_neon_request_exec(php_http_request_t *h, php_http_request_method_t meth_id, const char *url, php_http_message_body_t *body)
610 {
611 unsigned tries = 0;
612 STATUS retval = SUCCESS;
613 int result;
614 php_url *purl;
615 const char *meth;
616 ne_session *session;
617 ne_request *request;
618 php_http_neon_request_t *neon = h->ctx;
619 TSRMLS_FETCH_FROM_CTX(h->ts);
620
621 if (!(meth = php_http_request_method_name(meth_id TSRMLS_CC))) {
622 php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST_METHOD, "Unsupported request method: %d (%s)", meth, url);
623 return FAILURE;
624 }
625
626 if (!(purl = php_url_parse(url))) {
627 php_http_error(HE_WARNING, PHP_HTTP_E_URL, "Could not parse url %s", url);
628 return FAILURE;
629 }
630
631 if (neon->options.port) {
632 purl->port = neon->options.port;
633 } else if (!purl->port) {
634 purl->port = 80;
635 if (strncasecmp(purl->scheme, "http", 4)) {
636 #ifdef HAVE_GETSERVBYNAME
637 struct servent *se;
638
639 if ((se = getservbyname(purl->scheme, "tcp")) && se->s_port) {
640 purl->port = ntohs(se->s_port);
641 }
642 #endif
643 } else if (purl->scheme[4] == 's') {
644 purl->port = 443;
645 }
646 }
647
648 /* never returns NULL */
649 session = ne_session_create(purl->scheme, purl->host, purl->port);
650 if (neon->options.proxy.host) {
651 switch (neon->options.proxy.type) {
652 case NE_SOCK_SOCKSV4:
653 case NE_SOCK_SOCKSV4A:
654 case NE_SOCK_SOCKSV5:
655 ne_session_socks_proxy(session, neon->options.proxy.type, neon->options.proxy.host, neon->options.proxy.port, neon->options.auth.proxy.user, neon->options.auth.proxy.pass);
656 break;
657
658 default:
659 ne_session_proxy(session, neon->options.proxy.host, neon->options.proxy.port);
660 break;
661 }
662 }
663 if (neon->options.interface) {
664 ne_set_localaddr(session, neon->options.interface);
665 }
666 if (neon->options.useragent) {
667 ne_set_useragent(session, neon->options.useragent);
668 }
669 if (neon->options.timeout.read) {
670 ne_set_read_timeout(session, neon->options.timeout.read);
671 }
672 if (neon->options.timeout.read) {
673 ne_set_connect_timeout(session, neon->options.timeout.connect);
674 }
675 if (neon->options.redirects) {
676 ne_redirect_register(session);
677 }
678 ne_hook_pre_send(session, php_http_neon_pre_send_callback, h);
679 ne_hook_post_headers(session, php_http_neon_post_headers_callback, h);
680 ne_set_notifier(session, php_http_neon_progress_callback, h);
681 ne_ssl_set_verify(session, php_http_neon_ssl_verify_callback, h);
682 if (neon->options.ssl.clicert) {
683 ne_ssl_set_clicert(session, neon->options.ssl.clicert);
684 }
685 /* this crashes
686 ne_ssl_trust_default_ca(session); */
687 if (neon->options.ssl.trucert) {
688 ne_ssl_trust_cert(session, neon->options.ssl.trucert);
689 }
690
691 request = ne_request_create(session, meth, purl->path /* . purl->query */);
692 if (body) {
693 /* RFC2616, section 4.3 (para. 4) states that »a message-body MUST NOT be included in a request if the
694 * specification of the request method (section 5.1.1) does not allow sending an entity-body in request.«
695 * Following the clause in section 5.1.1 (para. 2) that request methods »MUST be implemented with the
696 * same semantics as those specified in section 9« reveal that not any single defined HTTP/1.1 method
697 * does not allow a request body.
698 */
699 switch (meth_id) {
700 default:
701 neon->body = body;
702 ne_set_request_body_provider(request, php_http_message_body_size(body), php_http_neon_read_callback, h);
703 break;
704 }
705 }
706
707 retry:
708 switch (result = ne_begin_request(request)) {
709 case NE_OK: {
710 ssize_t len;
711 char *buf = emalloc(0x1000);
712
713 while (0 < (len = ne_read_response_block(request, buf, 0x1000))) {
714 php_http_buffer_append(h->buffer, buf, len);
715 php_http_message_parser_parse(h->parser, h->buffer, PHP_HTTP_MESSAGE_PARSER_DUMB_BODIES, &h->message);
716 // php_http_message_body_append(&h->message->body, buf, len);
717 }
718
719 efree(buf);
720 break;
721 }
722
723 case NE_REDIRECT:
724 if (neon->options.redirects-- > 0){
725 const ne_uri *uri = ne_redirect_location(session);
726
727 if (uri) {
728 char *url = ne_uri_unparse(uri);
729
730 retval = php_http_neon_request_exec(h, meth_id, url, body);
731 free(url);
732 }
733 }
734 break;
735
736 default:
737 php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST, "%s; (%s)", ne_get_error(session), url);
738 if (EG(exception)) {
739 add_property_long(EG(exception), "neonCode", result);
740 }
741 retval = FAILURE;
742 break;
743 }
744
745 switch (result = ne_end_request(request)) {
746 case NE_OK:
747 break;
748
749 case NE_RETRY:
750 if (neon->options.retry.count > tries++) {
751 if (neon->options.retry.delay >= PHP_HTTP_DIFFSEC) {
752 php_http_sleep(neon->options.retry.delay);
753 }
754 goto retry;
755 break;
756 }
757
758 default:
759 php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST, "%s; (%s)", ne_get_error(session), url);
760 if (EG(exception)) {
761 add_property_long(EG(exception), "neonCode", result);
762 }
763 retval = FAILURE;
764 break;
765 }
766
767 ne_session_destroy(session);
768 php_url_free(purl);
769
770 return retval;
771 }
772
773 static STATUS php_http_neon_request_setopt(php_http_request_t *h, php_http_request_setopt_opt_t opt, void *arg)
774 {
775 php_http_neon_request_t *neon = h->ctx;
776 TSRMLS_FETCH_FROM_CTX(h->ts);
777
778 switch (opt) {
779 case PHP_HTTP_REQUEST_OPT_SETTINGS:
780 return set_options(h, arg);
781 break;
782
783 case PHP_HTTP_REQUEST_OPT_PROGRESS_CALLBACK:
784 if (neon->progress.in_cb) {
785 php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST, "Cannot change progress callback while executing it");
786 return FAILURE;
787 }
788 if (neon->progress.callback) {
789 php_http_request_progress_dtor(&neon->progress TSRMLS_CC);
790 }
791 neon->progress.callback = arg;
792 break;
793
794 case PHP_HTTP_REQUEST_OPT_COOKIES_ENABLE:
795 case PHP_HTTP_REQUEST_OPT_COOKIES_RESET:
796 case PHP_HTTP_REQUEST_OPT_COOKIES_RESET_SESSION:
797 case PHP_HTTP_REQUEST_OPT_COOKIES_FLUSH:
798 /* still NOOPs */
799 break;
800
801 default:
802 return FAILURE;
803 }
804
805 return SUCCESS;
806 }
807
808 static STATUS php_http_neon_request_getopt(php_http_request_t *h, php_http_request_getopt_opt_t opt, void *arg)
809 {
810 php_http_neon_request_t *neon = h->ctx;
811
812 switch (opt) {
813 case PHP_HTTP_REQUEST_OPT_PROGRESS_INFO:
814 *((php_http_request_progress_t **) arg) = &neon->progress;
815 break;
816
817 case PHP_HTTP_REQUEST_OPT_TRANSFER_INFO:
818 break;
819
820 default:
821 return FAILURE;
822 }
823
824 return SUCCESS;
825 }
826
827 static php_http_resource_factory_ops_t php_http_neon_resource_factory_ops = {
828 NULL,
829 NULL,
830 NULL
831 };
832
833 static php_http_request_ops_t php_http_neon_request_ops = {
834 &php_http_neon_resource_factory_ops,
835 php_http_neon_request_init,
836 php_http_neon_request_copy,
837 php_http_neon_request_dtor,
838 php_http_neon_request_reset,
839 php_http_neon_request_exec,
840 php_http_neon_request_setopt,
841 php_http_neon_request_getopt
842 };
843
844 PHP_HTTP_API php_http_request_ops_t *php_http_neon_get_request_ops(void)
845 {
846 return &php_http_neon_request_ops;
847 }
848
849 #define PHP_HTTP_BEGIN_ARGS(method, req_args) PHP_HTTP_BEGIN_ARGS_EX(HttpNEON, method, 0, req_args)
850 #define PHP_HTTP_EMPTY_ARGS(method) PHP_HTTP_EMPTY_ARGS_EX(HttpNEON, method, 0)
851 #define PHP_HTTP_NEON_ME(method, visibility) PHP_ME(HttpNEON, method, PHP_HTTP_ARGS(HttpNEON, method), visibility)
852 #define PHP_HTTP_NEON_ALIAS(method, func) PHP_HTTP_STATIC_ME_ALIAS(method, func, PHP_HTTP_ARGS(HttpNEON, method))
853 #define PHP_HTTP_NEON_MALIAS(me, al, vis) ZEND_FENTRY(me, ZEND_MN(HttpNEON_##al), PHP_HTTP_ARGS(HttpNEON, al), vis)
854
855 PHP_HTTP_EMPTY_ARGS(__construct);
856
857 zend_class_entry *php_http_neon_class_entry;
858 zend_function_entry php_http_neon_method_entry[] = {
859 PHP_HTTP_NEON_ME(__construct, ZEND_ACC_PRIVATE|ZEND_ACC_CTOR)
860
861 EMPTY_FUNCTION_ENTRY
862 };
863
864 PHP_METHOD(HttpNEON, __construct) {
865 }
866
867
868 PHP_MINIT_FUNCTION(http_neon)
869 {
870 php_http_request_factory_driver_t driver = {
871 &php_http_neon_request_ops,
872 NULL
873 };
874 if (SUCCESS != php_http_request_factory_add_driver(ZEND_STRL("neon"), &driver)) {
875 return FAILURE;
876 }
877
878 PHP_HTTP_REGISTER_CLASS(http, NEON, http_neon, php_http_neon_class_entry, 0);
879
880 /*
881 * Auth Constants
882 */
883 zend_declare_class_constant_long(php_http_neon_class_entry, ZEND_STRL("AUTH_BASIC"), NE_AUTH_BASIC TSRMLS_CC);
884 zend_declare_class_constant_long(php_http_neon_class_entry, ZEND_STRL("AUTH_DIGEST"), NE_AUTH_DIGEST TSRMLS_CC);
885 zend_declare_class_constant_long(php_http_neon_class_entry, ZEND_STRL("AUTH_NTLM"), NE_AUTH_NTLM TSRMLS_CC);
886 zend_declare_class_constant_long(php_http_neon_class_entry, ZEND_STRL("AUTH_GSSAPI"), NE_AUTH_GSSAPI TSRMLS_CC);
887 zend_declare_class_constant_long(php_http_neon_class_entry, ZEND_STRL("AUTH_GSSNEG"), NE_AUTH_NEGOTIATE TSRMLS_CC);
888 zend_declare_class_constant_long(php_http_neon_class_entry, ZEND_STRL("AUTH_ANY"), NE_AUTH_ALL TSRMLS_CC);
889
890 /*
891 * Proxy Type Constants
892 */
893 zend_declare_class_constant_long(php_http_neon_class_entry, ZEND_STRL("PROXY_SOCKS4"), NE_SOCK_SOCKSV4 TSRMLS_CC);
894 zend_declare_class_constant_long(php_http_neon_class_entry, ZEND_STRL("PROXY_SOCKS4A"), NE_SOCK_SOCKSV4A TSRMLS_CC);
895 zend_declare_class_constant_long(php_http_neon_class_entry, ZEND_STRL("PROXY_SOCKS5"), NE_SOCK_SOCKSV5 TSRMLS_CC);
896 zend_declare_class_constant_long(php_http_neon_class_entry, ZEND_STRL("PROXY_HTTP"), -1 TSRMLS_CC);
897
898 if (NE_OK != ne_sock_init()) {
899 return FAILURE;
900 }
901 return SUCCESS;
902 }
903
904 PHP_MSHUTDOWN_FUNCTION(http_neon)
905 {
906 ne_sock_exit();
907 return SUCCESS;
908 }
909
910 #endif
911
912 /*
913 * Local variables:
914 * tab-width: 4
915 * c-basic-offset: 4
916 * End:
917 * vim600: noet sw=4 ts=4 fdm=marker
918 * vim<600: noet sw=4 ts=4
919 */
920