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