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