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