* attempt to fix php4 ref handling
[m6w6/ext-http] / http.c
1 /*
2 +----------------------------------------------------------------------+
3 | PECL :: http |
4 +----------------------------------------------------------------------+
5 | This source file is subject to version 3.0 of the PHP license, that |
6 | is bundled with this package in the file LICENSE, and is available |
7 | through the world-wide-web at http://www.php.net/license/3_0.txt. |
8 | If you did not receive a copy of the PHP license and are unable to |
9 | obtain it through the world-wide-web, please send a note to |
10 | license@php.net so we can mail you a copy immediately. |
11 +----------------------------------------------------------------------+
12 | Copyright (c) 2004-2005 Michael Wallner <mike@php.net> |
13 +----------------------------------------------------------------------+
14 */
15
16 /* $Id$ */
17
18 #define ZEND_INCLUDE_FULL_WINDOWS_HEADERS
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 #include "php.h"
25 #include "snprintf.h"
26 #include "ext/standard/info.h"
27 #include "ext/session/php_session.h"
28 #include "ext/standard/php_string.h"
29 #include "ext/standard/php_smart_str.h"
30
31 #include "php_http.h"
32 #include "php_http_api.h"
33
34 #ifdef ZEND_ENGINE_2
35 #include "ext/standard/php_http.h"
36 #endif
37
38 #ifdef HTTP_HAVE_CURL
39
40 #ifdef PHP_WIN32
41 #include <winsock2.h>
42 #include <sys/types.h>
43 #endif
44
45 #include <curl/curl.h>
46 #endif
47
48 ZEND_DECLARE_MODULE_GLOBALS(http)
49
50 #ifdef COMPILE_DL_HTTP
51 ZEND_GET_MODULE(http)
52 #endif
53
54 /* {{{ ARG_INFO */
55 #ifdef ZEND_BEGIN_ARG_INFO
56 ZEND_BEGIN_ARG_INFO(http_request_info_ref_3, 0)
57 ZEND_ARG_PASS_INFO(0)
58 ZEND_ARG_PASS_INFO(0)
59 ZEND_ARG_PASS_INFO(1)
60 ZEND_END_ARG_INFO();
61
62 ZEND_BEGIN_ARG_INFO(http_request_info_ref_4, 0)
63 ZEND_ARG_PASS_INFO(0)
64 ZEND_ARG_PASS_INFO(0)
65 ZEND_ARG_PASS_INFO(0)
66 ZEND_ARG_PASS_INFO(1)
67 ZEND_END_ARG_INFO();
68 #else
69 static unsigned char http_request_info_ref_3[] = {3, BYREF_NONE, BYREF_NONE, BYREF_FORCE};
70 static unsigned char http_request_info_ref_4[] = {4, BYREF_NONE, BYREF_NONE, BYREF_NONE, BYREF_FORCE};
71 #endif
72 /* }}}*/
73
74 /* {{{ http_functions[] */
75 function_entry http_functions[] = {
76 PHP_FE(http_date, NULL)
77 PHP_FE(http_absolute_uri, NULL)
78 PHP_FE(http_negotiate_language, NULL)
79 PHP_FE(http_negotiate_charset, NULL)
80 PHP_FE(http_redirect, NULL)
81 PHP_FE(http_send_status, NULL)
82 PHP_FE(http_send_last_modified, NULL)
83 PHP_FE(http_match_modified, NULL)
84 PHP_FE(http_match_etag, NULL)
85 PHP_FE(http_cache_last_modified, NULL)
86 PHP_FE(http_cache_etag, NULL)
87 PHP_FE(http_content_type, NULL)
88 PHP_FE(http_content_disposition, NULL)
89 PHP_FE(http_send_data, NULL)
90 PHP_FE(http_send_file, NULL)
91 PHP_FE(http_send_stream, NULL)
92 PHP_FE(http_chunked_decode, NULL)
93 PHP_FE(http_split_response, NULL)
94 PHP_FE(http_parse_headers, NULL)
95 #ifdef HTTP_HAVE_CURL
96 PHP_FE(http_get, http_request_info_ref_3)
97 PHP_FE(http_head, http_request_info_ref_3)
98 PHP_FE(http_post_data, http_request_info_ref_4)
99 PHP_FE(http_post_array, http_request_info_ref_4)
100 #endif
101 PHP_FE(http_auth_basic, NULL)
102 PHP_FE(http_auth_basic_cb, NULL)
103 #ifndef ZEND_ENGINE_2
104 PHP_FE(http_build_query, NULL)
105 #endif
106 {NULL, NULL, NULL}
107 };
108 /* }}} */
109
110 /* {{{ http_module_entry */
111 zend_module_entry http_module_entry = {
112 #if ZEND_MODULE_API_NO >= 20010901
113 STANDARD_MODULE_HEADER,
114 #endif
115 "http",
116 http_functions,
117 PHP_MINIT(http),
118 NULL,
119 NULL,
120 PHP_RSHUTDOWN(http),
121 PHP_MINFO(http),
122 #if ZEND_MODULE_API_NO >= 20010901
123 PHP_EXT_HTTP_VERSION,
124 #endif
125 STANDARD_MODULE_PROPERTIES
126 };
127 /* }}} */
128
129 #define RETURN_SUCCESS(v) RETURN_BOOL(SUCCESS == (v))
130 #define HASH_ORNULL(z) ((z) ? Z_ARRVAL_P(z) : NULL)
131
132 /* {{{ proto string http_date([int timestamp])
133 *
134 * This function returns a valid HTTP date regarding RFC 822/1123
135 * looking like: "Wed, 22 Dec 2004 11:34:47 GMT"
136 *
137 */
138 PHP_FUNCTION(http_date)
139 {
140 long t = -1;
141
142 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|l", &t) != SUCCESS) {
143 RETURN_FALSE;
144 }
145
146 if (t == -1) {
147 t = (long) time(NULL);
148 }
149
150 RETURN_STRING(http_date(t), 0);
151 }
152 /* }}} */
153
154 /* {{{ proto string http_absolute_uri(string url[, string proto])
155 *
156 * This function returns an absolute URI constructed from url.
157 * If the url is already abolute but a different proto was supplied,
158 * only the proto part of the URI will be updated. If url has no
159 * path specified, the path of the current REQUEST_URI will be taken.
160 * The host will be taken either from the Host HTTP header of the client
161 * the SERVER_NAME or just localhost if prior are not available.
162 *
163 * Some examples:
164 * <pre>
165 * url = "page.php" => http://www.example.com/current/path/page.php
166 * url = "/page.php" => http://www.example.com/page.php
167 * url = "/page.php", proto = "https" => https://www.example.com/page.php
168 * </pre>
169 *
170 */
171 PHP_FUNCTION(http_absolute_uri)
172 {
173 char *url = NULL, *proto = NULL;
174 int url_len = 0, proto_len = 0;
175
176 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|s", &url, &url_len, &proto, &proto_len) != SUCCESS) {
177 RETURN_FALSE;
178 }
179
180 RETURN_STRING(http_absolute_uri(url, proto), 0);
181 }
182 /* }}} */
183
184 /* {{{ proto string http_negotiate_language(array supported[, string default = 'en-US'])
185 *
186 * This function negotiates the clients preferred language based on its
187 * Accept-Language HTTP header. It returns the negotiated language or
188 * the default language if none match.
189 *
190 * The qualifier is recognized and languages without qualifier are rated highest.
191 *
192 * The supported parameter is expected to be an array having
193 * the supported languages as array values.
194 *
195 * Example:
196 * <pre>
197 * <?php
198 * $langs = array(
199 * 'en-US',// default
200 * 'fr',
201 * 'fr-FR',
202 * 'de',
203 * 'de-DE',
204 * 'de-AT',
205 * 'de-CH',
206 * );
207 * include './langs/'. http_negotiate_language($langs) .'.php';
208 * ?>
209 * </pre>
210 *
211 */
212 PHP_FUNCTION(http_negotiate_language)
213 {
214 zval *supported;
215 char *def = NULL;
216 int def_len = 0;
217
218 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a|s", &supported, &def, &def_len) != SUCCESS) {
219 RETURN_FALSE;
220 }
221
222 if (!def) {
223 def = "en-US";
224 }
225
226 RETURN_STRING(http_negotiate_language(supported, def), 0);
227 }
228 /* }}} */
229
230 /* {{{ proto string http_negotiate_charset(array supported[, string default = 'iso-8859-1'])
231 *
232 * This function negotiates the clients preferred charset based on its
233 * Accept-Charset HTTP header. It returns the negotiated charset or
234 * the default charset if none match.
235 *
236 * The qualifier is recognized and charset without qualifier are rated highest.
237 *
238 * The supported parameter is expected to be an array having
239 * the supported charsets as array values.
240 *
241 * Example:
242 * <pre>
243 * <?php
244 * $charsets = array(
245 * 'iso-8859-1', // default
246 * 'iso-8859-2',
247 * 'iso-8859-15',
248 * 'utf-8'
249 * );
250 * $pref = http_negotiate_charset($charsets);
251 * if (!strcmp($pref, 'iso-8859-1')) {
252 * iconv_set_encoding('internal_encoding', 'iso-8859-1');
253 * iconv_set_encoding('output_encoding', $pref);
254 * ob_start('ob_iconv_handler');
255 * }
256 * ?>
257 * </pre>
258 */
259 PHP_FUNCTION(http_negotiate_charset)
260 {
261 zval *supported;
262 char *def = NULL;
263 int def_len = 0;
264
265 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a|s", &supported, &def, &def_len) != SUCCESS) {
266 RETURN_FALSE;
267 }
268
269 if (!def) {
270 def = "iso-8859-1";
271 }
272
273 RETURN_STRING(http_negotiate_charset(supported, def), 0);
274 }
275 /* }}} */
276
277 /* {{{ proto bool http_send_status(int status)
278 *
279 * Send HTTP status code.
280 *
281 */
282 PHP_FUNCTION(http_send_status)
283 {
284 int status = 0;
285
286 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &status) != SUCCESS) {
287 RETURN_FALSE;
288 }
289 if (status < 100 || status > 510) {
290 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid HTTP status code (100-510): %d", status);
291 RETURN_FALSE;
292 }
293
294 RETURN_SUCCESS(http_send_status(status));
295 }
296 /* }}} */
297
298 /* {{{ proto bool http_send_last_modified([int timestamp])
299 *
300 * This converts the given timestamp to a valid HTTP date and
301 * sends it as "Last-Modified" HTTP header. If timestamp is
302 * omitted, current time is sent.
303 *
304 */
305 PHP_FUNCTION(http_send_last_modified)
306 {
307 long t = -1;
308
309 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|l", &t) != SUCCESS) {
310 RETURN_FALSE;
311 }
312
313 if (t == -1) {
314 t = (long) time(NULL);
315 }
316
317 RETURN_SUCCESS(http_send_last_modified(t));
318 }
319 /* }}} */
320
321 /* {{{ proto bool http_match_modified([int timestamp])
322 *
323 * Matches the given timestamp against the clients "If-Modified-Since" resp.
324 * "If-Unmodified-Since" HTTP headers.
325 *
326 */
327 PHP_FUNCTION(http_match_modified)
328 {
329 long t = -1;
330
331 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|l", &t) != SUCCESS) {
332 RETURN_FALSE;
333 }
334
335 // current time if not supplied (senseless though)
336 if (t == -1) {
337 t = (long) time(NULL);
338 }
339
340 RETURN_BOOL(http_modified_match("HTTP_IF_MODIFIED_SINCE", t) || http_modified_match("HTTP_IF_UNMODIFIED_SINCE", t));
341 }
342 /* }}} */
343
344 /* {{{ proto bool http_match_etag(string etag)
345 *
346 * This matches the given ETag against the clients
347 * "If-Match" resp. "If-None-Match" HTTP headers.
348 *
349 */
350 PHP_FUNCTION(http_match_etag)
351 {
352 int etag_len;
353 char *etag;
354
355 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &etag, &etag_len) != SUCCESS) {
356 RETURN_FALSE;
357 }
358
359 RETURN_BOOL(http_etag_match("HTTP_IF_NONE_MATCH", etag) || http_etag_match("HTTP_IF_MATCH", etag));
360 }
361 /* }}} */
362
363 /* {{{ proto bool http_cache_last_modified([int timestamp_or_expires]])
364 *
365 * If timestamp_or_exires is greater than 0, it is handled as timestamp
366 * and will be sent as date of last modification. If it is 0 or omitted,
367 * the current time will be sent as Last-Modified date. If it's negative,
368 * it is handled as expiration time in seconds, which means that if the
369 * requested last modification date is not between the calculated timespan,
370 * the Last-Modified header is updated and the actual body will be sent.
371 *
372 */
373 PHP_FUNCTION(http_cache_last_modified)
374 {
375 long last_modified = 0, send_modified = 0, t;
376 zval *zlm;
377
378 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|l", &last_modified) != SUCCESS) {
379 RETURN_FALSE;
380 }
381
382 t = (long) time(NULL);
383
384 /* 0 or omitted */
385 if (!last_modified) {
386 /* does the client have? (att: caching "forever") */
387 if (zlm = http_get_server_var("HTTP_IF_MODIFIED_SINCE")) {
388 last_modified = send_modified = http_parse_date(Z_STRVAL_P(zlm));
389 /* send current time */
390 } else {
391 send_modified = t;
392 }
393 /* negative value is supposed to be expiration time */
394 } else if (last_modified < 0) {
395 last_modified += t;
396 send_modified = t;
397 /* send supplied time explicitly */
398 } else {
399 send_modified = last_modified;
400 }
401
402 http_send_header("Cache-Control: private, must-revalidate, max-age=0");
403
404 if (http_modified_match("HTTP_IF_MODIFIED_SINCE", last_modified)) {
405 if (SUCCESS == http_send_status(304)) {
406 zend_bailout();
407 } else {
408 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not send 304 Not Modified");
409 RETURN_FALSE;
410 }
411 }
412 RETURN_SUCCESS(http_send_last_modified(send_modified));
413 }
414 /* }}} */
415
416 /* {{{ proto bool http_cache_etag([string etag])
417 *
418 * This function attempts to cache the HTTP body based on an ETag,
419 * either supplied or generated through calculation of the MD5
420 * checksum of the output (uses output buffering).
421 *
422 * If clients "If-None-Match" header matches the supplied/calculated
423 * ETag, the body is considered cached on the clients side and
424 * a "304 Not Modified" status code is issued.
425 *
426 */
427 PHP_FUNCTION(http_cache_etag)
428 {
429 char *etag;
430 int etag_len = 0;
431
432 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s", &etag, &etag_len) != SUCCESS) {
433 RETURN_FALSE;
434 }
435
436 php_end_ob_buffers(0 TSRMLS_CC);
437 http_send_header("Cache-Control: private, must-revalidate, max-age=0");
438
439 /* if no etag is given and we didn't already
440 * start ob_etaghandler -- start it
441 */
442 if (!HTTP_G(etag_started) && !etag_len) {
443 php_ob_set_internal_handler(_http_ob_etaghandler, (uint) 4096, "etag output handler", 0 TSRMLS_CC);
444 HTTP_G(etag_started) = 1;
445 RETURN_BOOL(php_start_ob_buffer_named("etag output handler", (uint) 4096, 0 TSRMLS_CC));
446 }
447
448 if (http_etag_match("HTTP_IF_NONE_MATCH", etag)) {
449 if (SUCCESS == http_send_status(304)) {
450 zend_bailout();
451 } else {
452 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not send 304 Not Modified");
453 RETURN_FALSE;
454 }
455 }
456
457 RETURN_SUCCESS(http_send_etag(etag, etag_len));
458 }
459 /* }}} */
460
461 /* {{{ proto void http_redirect([string url[, array params[, bool session,[ bool permanent]]]])
462 *
463 * Redirect to a given url.
464 * The supplied url will be expanded with http_absolute_uri(), the params array will
465 * be treated with http_build_query() and the session identification will be appended
466 * if session is true.
467 *
468 * Depending on permanent the redirection will be issued with a permanent
469 * ("301 Moved Permanently") or a temporary ("302 Found") redirection
470 * status code.
471 *
472 * To be RFC compliant, "Redirecting to <a>URI</a>." will be displayed,
473 * if the client doesn't redirect immediatly.
474 */
475 PHP_FUNCTION(http_redirect)
476 {
477 int url_len;
478 zend_bool session = 0, permanent = 0;
479 zval *params = NULL;
480 smart_str qstr = {0};
481 char *url, *URI, LOC[HTTP_URI_MAXLEN + 9], RED[HTTP_URI_MAXLEN * 2 + 34];
482
483 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|sa!/bb", &url, &url_len, &params, &session, &permanent) != SUCCESS) {
484 RETURN_FALSE;
485 }
486
487 /* append session info */
488 if (session && (PS(session_status) == php_session_active)) {
489 if (!params) {
490 MAKE_STD_ZVAL(params);
491 array_init(params);
492 }
493 if (add_assoc_string(params, PS(session_name), PS(id), 1) != SUCCESS) {
494 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not append session information");
495 }
496 }
497
498 /* treat params array with http_build_query() */
499 if (params) {
500 if (php_url_encode_hash_ex(Z_ARRVAL_P(params), &qstr, NULL,0,NULL,0,NULL,0,NULL TSRMLS_CC) != SUCCESS) {
501 if (qstr.c) {
502 efree(qstr.c);
503 }
504 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not encode query parameters");
505 RETURN_FALSE;
506 }
507 smart_str_0(&qstr);
508 }
509
510 URI = http_absolute_uri(url, NULL);
511 if (qstr.c) {
512 snprintf(LOC, HTTP_URI_MAXLEN + strlen("Location: "), "Location: %s?%s", URI, qstr.c);
513 sprintf(RED, "Redirecting to <a href=\"%s?%s\">%s?%s</a>.\n", URI, qstr.c, URI, qstr.c);
514 efree(qstr.c);
515 } else {
516 snprintf(LOC, HTTP_URI_MAXLEN + strlen("Location: "), "Location: %s", URI);
517 sprintf(RED, "Redirecting to <a href=\"%s\">%s</a>.\n", URI, URI);
518 }
519 efree(URI);
520
521 if ((SUCCESS == http_send_header(LOC)) && (SUCCESS == http_send_status((permanent ? 301 : 302)))) {
522 php_body_write(RED, strlen(RED) TSRMLS_CC);
523 RETURN_TRUE;
524 }
525 RETURN_FALSE;
526 }
527 /* }}} */
528
529 /* {{{ proto bool http_send_data(string data)
530 *
531 * Sends raw data with support for (multiple) range requests.
532 *
533 */
534 PHP_FUNCTION(http_send_data)
535 {
536 zval *zdata;
537
538 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &zdata) != SUCCESS) {
539 RETURN_FALSE;
540 }
541
542 convert_to_string_ex(&zdata);
543 http_send_header("Accept-Ranges: bytes");
544 RETURN_SUCCESS(http_send_data(zdata));
545 }
546 /* }}} */
547
548 /* {{{ proto bool http_send_file(string file)
549 *
550 * Sends a file with support for (multiple) range requests.
551 *
552 */
553 PHP_FUNCTION(http_send_file)
554 {
555 zval *zfile;
556
557 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &zfile) != SUCCESS) {
558 RETURN_FALSE;
559 }
560
561 convert_to_string_ex(&zfile);
562 http_send_header("Accept-Ranges: bytes");
563 RETURN_SUCCESS(http_send_file(zfile));
564 }
565 /* }}} */
566
567 /* {{{ proto bool http_send_stream(resource stream)
568 *
569 * Sends an already opened stream with support for (multiple) range requests.
570 *
571 */
572 PHP_FUNCTION(http_send_stream)
573 {
574 zval *zstream;
575 php_stream *file;
576
577 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &zstream) != SUCCESS) {
578 RETURN_FALSE;
579 }
580
581 php_stream_from_zval(file, &zstream);
582 http_send_header("Accept-Ranges: bytes");
583 RETURN_SUCCESS(http_send_stream(file));
584 }
585 /* }}} */
586
587 /* {{{ proto bool http_content_type([string content_type = 'application/x-octetstream'])
588 *
589 * Sets the content type.
590 *
591 */
592 PHP_FUNCTION(http_content_type)
593 {
594 char *ct, *content_type;
595 int ct_len = 0;
596
597 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s", &ct, &ct_len) != SUCCESS) {
598 RETURN_FALSE;
599 }
600
601 if (!ct_len) {
602 RETURN_SUCCESS(http_send_header("Content-Type: application/x-octetstream"));
603 }
604
605 /* remember for multiple ranges */
606 if (HTTP_G(ctype)) {
607 efree(HTTP_G(ctype));
608 }
609 HTTP_G(ctype) = estrndup(ct, ct_len);
610
611 content_type = (char *) emalloc(strlen("Content-Type: ") + ct_len + 1);
612 sprintf(content_type, "Content-Type: %s", ct);
613
614 RETVAL_BOOL(SUCCESS == http_send_header(content_type));
615 efree(content_type);
616 }
617 /* }}} */
618
619 /* {{{ proto bool http_content_disposition(string filename[, bool inline = false])
620 *
621 * Set the Content Disposition. The Content-Disposition header is very useful
622 * if the data actually sent came from a file or something similar, that should
623 * be "saved" by the client/user (i.e. by browsers "Save as..." popup window).
624 *
625 */
626 PHP_FUNCTION(http_content_disposition)
627 {
628 char *filename, *header;
629 int f_len;
630 zend_bool send_inline = 0;
631
632 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|b", &filename, &f_len, &send_inline) != SUCCESS) {
633 RETURN_FALSE;
634 }
635
636 if (send_inline) {
637 header = (char *) emalloc(strlen("Content-Disposition: inline; filename=\"\"") + f_len + 1);
638 sprintf(header, "Content-Disposition: inline; filename=\"%s\"", filename);
639 } else {
640 header = (char *) emalloc(strlen("Content-Disposition: attachment; filename=\"\"") + f_len + 1);
641 sprintf(header, "Content-Disposition: attachment; filename=\"%s\"", filename);
642 }
643
644 RETVAL_BOOL(SUCCESS == http_send_header(header));
645 efree(header);
646 }
647 /* }}} */
648
649 /* {{{ proto string http_chunked_decode(string encoded)
650 *
651 * This function decodes a string that was HTTP-chunked encoded.
652 * Returns false on failure.
653 */
654 PHP_FUNCTION(http_chunked_decode)
655 {
656 char *encoded = NULL, *decoded = NULL;
657 int encoded_len = 0, decoded_len = 0;
658
659 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &encoded, &encoded_len) != SUCCESS) {
660 RETURN_FALSE;
661 }
662
663 if (SUCCESS == http_chunked_decode(encoded, encoded_len, &decoded, &decoded_len)) {
664 RETURN_STRINGL(decoded, decoded_len, 0);
665 } else {
666 RETURN_FALSE;
667 }
668 }
669 /* }}} */
670
671 /* {{{ proto array http_split_response(string http_response)
672 *
673 * This function splits an HTTP response into an array with headers and the
674 * content body. The returned array may look simliar to the following example:
675 *
676 * <pre>
677 * array(
678 * 0 => array(
679 * 'Status' => '200 Ok',
680 * 'Content-Type' => 'text/plain',
681 * 'Content-Language' => 'en-US'
682 * ),
683 * 1 => "Hello World!"
684 * );
685 * </pre>
686 */
687 PHP_FUNCTION(http_split_response)
688 {
689 zval *zresponse, *zbody, *zheaders;
690
691 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &zresponse) != SUCCESS) {
692 RETURN_FALSE;
693 }
694
695 convert_to_string_ex(&zresponse);
696
697 MAKE_STD_ZVAL(zbody);
698 MAKE_STD_ZVAL(zheaders);
699 array_init(zheaders);
700
701 if (SUCCESS != http_split_response(zresponse, zheaders, zbody)) {
702 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not parse HTTP response");
703 RETURN_FALSE;
704 }
705
706 array_init(return_value);
707 add_index_zval(return_value, 0, zheaders);
708 add_index_zval(return_value, 1, zbody);
709 }
710 /* }}} */
711
712 /* {{{ proto array http_parse_headers(string header) */
713 PHP_FUNCTION(http_parse_headers)
714 {
715 char *header, *rnrn;
716 int header_len;
717
718 if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &header, &header_len)) {
719 RETURN_FALSE;
720 }
721
722 array_init(return_value);
723
724 if (rnrn = strstr(header, HTTP_CRLF HTTP_CRLF)) {
725 header_len = rnrn - header + 2;
726 }
727 if (SUCCESS != http_parse_headers(header, header_len, return_value)) {
728 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not parse HTTP header");
729 zval_dtor(return_value);
730 RETURN_FALSE;
731 }
732 }
733 /* }}}*/
734
735 /* {{{ HAVE_CURL */
736 #ifdef HTTP_HAVE_CURL
737
738 /* {{{ proto string http_get(string url[, array options[, array &info]])
739 *
740 * Performs an HTTP GET request on the supplied url.
741 *
742 * The second parameter is expected to be an associative
743 * array where the following keys will be recognized:
744 * <pre>
745 * - redirect: int, whether and how many redirects to follow
746 * - unrestrictedauth: bool, whether to continue sending credentials on
747 * redirects to a different host
748 * - proxyhost: string, proxy host in "host[:port]" format
749 * - proxyport: int, use another proxy port as specified in proxyhost
750 * - proxyauth: string, proxy credentials in "user:pass" format
751 * - proxyauthtype: int, HTTP_AUTH_BASIC and/or HTTP_AUTH_NTLM
752 * - httpauth: string, http credentials in "user:pass" format
753 * - httpauthtype: int, HTTP_AUTH_BASIC, DIGEST and/or NTLM
754 * - compress: bool, whether to allow gzip/deflate content encoding
755 * (defaults to true)
756 * - port: int, use another port as specified in the url
757 * - referer: string, the referer to sends
758 * - useragent: string, the user agent to send
759 * (defaults to PECL::HTTP/version (PHP/version)))
760 * - headers: array, list of custom headers as associative array
761 * like array("header" => "value")
762 * - cookies: array, list of cookies as associative array
763 * like array("cookie" => "value")
764 * - cookiestore: string, path to a file where cookies are/will be stored
765 * </pre>
766 *
767 * The optional third parameter will be filled with some additional information
768 * in form af an associative array, if supplied (don't forget to initialize it
769 * with NULL or array()).
770 */
771 PHP_FUNCTION(http_get)
772 {
773 char *URL, *data = NULL;
774 size_t data_len = 0;
775 int URL_len;
776 zval *options = NULL, *info = NULL;
777
778 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|a/!z", &URL, &URL_len, &options, &info) != SUCCESS) {
779 RETURN_FALSE;
780 }
781
782 if (info) {
783 zval_dtor(info);
784 array_init(info);
785 }
786
787 if (SUCCESS == http_get(URL, HASH_ORNULL(options), HASH_ORNULL(info), &data, &data_len)) {
788 RETURN_STRINGL(data, data_len, 0);
789 } else {
790 RETURN_FALSE;
791 }
792 }
793 /* }}} */
794
795 /* {{{ proto string http_head(string url[, array options[, array &info]])
796 *
797 * Performs an HTTP HEAD request on the suppied url.
798 * Returns the HTTP response as string.
799 * See http_get() for a full list of available options.
800 */
801 PHP_FUNCTION(http_head)
802 {
803 char *URL, *data = NULL;
804 size_t data_len = 0;
805 int URL_len;
806 zval *options = NULL, *info = NULL;
807
808 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|a/!z", &URL, &URL_len, &options, &info) != SUCCESS) {
809 RETURN_FALSE;
810 }
811
812 if (info) {
813 zval_dtor(info);
814 array_init(info);
815 }
816
817 if (SUCCESS == http_head(URL, HASH_ORNULL(options), HASH_ORNULL(info), &data, &data_len)) {
818 RETURN_STRINGL(data, data_len, 0);
819 } else {
820 RETURN_FALSE;
821 }
822 }
823 /* }}} */
824
825 /* {{{ proto string http_post_data(string url, string data[, array options[, &info]])
826 *
827 * Performs an HTTP POST request, posting data.
828 * Returns the HTTP response as string.
829 * See http_get() for a full list of available options.
830 */
831 PHP_FUNCTION(http_post_data)
832 {
833 char *URL, *postdata, *data = NULL;
834 size_t data_len = 0;
835 int postdata_len, URL_len;
836 zval *options = NULL, *info = NULL;
837
838 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss|a/!z", &URL, &URL_len, &postdata, &postdata_len, &options, &info) != SUCCESS) {
839 RETURN_FALSE;
840 }
841
842 if (info) {
843 zval_dtor(info);
844 array_init(info);
845 }
846
847 if (SUCCESS == http_post_data(URL, postdata, (size_t) postdata_len, HASH_ORNULL(options), HASH_ORNULL(info), &data, &data_len)) {
848 RETURN_STRINGL(data, data_len, 0);
849 } else {
850 RETURN_FALSE;
851 }
852 }
853 /* }}} */
854
855 /* {{{ proto string http_post_array(string url, array data[, array options[, array &info]])
856 *
857 * Performs an HTTP POST request, posting www-form-urlencoded array data.
858 * Returns the HTTP response as string.
859 * See http_get() for a full list of available options.
860 */
861 PHP_FUNCTION(http_post_array)
862 {
863 char *URL, *data = NULL;
864 size_t data_len = 0;
865 int URL_len;
866 zval *options = NULL, *info = NULL, *postdata;
867
868 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sa|a/!z", &URL, &URL_len, &postdata, &options, &info) != SUCCESS) {
869 RETURN_FALSE;
870 }
871
872 if (info) {
873 zval_dtor(info);
874 array_init(info);
875 }
876
877 if (SUCCESS == http_post_array(URL, Z_ARRVAL_P(postdata), HASH_ORNULL(options), HASH_ORNULL(info), &data, &data_len)) {
878 RETURN_STRINGL(data, data_len, 0);
879 } else {
880 RETURN_FALSE;
881 }
882 }
883 /* }}} */
884
885 #endif
886 /* }}} HAVE_CURL */
887
888
889 /* {{{ proto bool http_auth_basic(string user, string pass[, string realm = "Restricted"])
890 *
891 * Example:
892 * <pre>
893 * <?php
894 * if (!http_auth_basic('mike', 's3c|r3t')) {
895 * die('<h1>Authorization failed!</h1>');
896 * }
897 * ?>
898 * </pre>
899 */
900 PHP_FUNCTION(http_auth_basic)
901 {
902 char *realm = NULL, *user, *pass, *suser, *spass;
903 int r_len, u_len, p_len;
904
905 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss|s", &user, &u_len, &pass, &p_len, &realm, &r_len) != SUCCESS) {
906 RETURN_FALSE;
907 }
908
909 if (!realm) {
910 realm = "Restricted";
911 }
912
913 if (SUCCESS != http_auth_credentials(&suser, &spass)) {
914 http_auth_header("Basic", realm);
915 RETURN_FALSE;
916 }
917
918 if (strcasecmp(suser, user)) {
919 http_auth_header("Basic", realm);
920 RETURN_FALSE;
921 }
922
923 if (strcmp(spass, pass)) {
924 http_auth_header("Basic", realm);
925 RETURN_FALSE;
926 }
927
928 RETURN_TRUE;
929 }
930 /* }}} */
931
932 /* {{{ proto bool http_auth_basic_cb(mixed callback[, string realm = "Restricted"])
933 *
934 * Example:
935 * <pre>
936 * <?php
937 * function auth_cb($user, $pass)
938 * {
939 * global $db;
940 * $query = 'SELECT pass FROM users WHERE user='. $db->quoteSmart($user);
941 * if (strlen($realpass = $db->getOne($query)) {
942 * return $pass === $realpass;
943 * }
944 * return false;
945 * }
946 *
947 * if (!http_auth_basic_cb('auth_cb')) {
948 * die('<h1>Authorization failed</h1>');
949 * }
950 * ?>
951 * </pre>
952 */
953 PHP_FUNCTION(http_auth_basic_cb)
954 {
955 zval *cb;
956 char *realm = NULL, *user, *pass;
957 int r_len;
958
959 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|s", &cb, &realm, &r_len) != SUCCESS) {
960 RETURN_FALSE;
961 }
962
963 if (!realm) {
964 realm = "Restricted";
965 }
966
967 if (SUCCESS != http_auth_credentials(&user, &pass)) {
968 http_auth_header("Basic", realm);
969 RETURN_FALSE;
970 }
971 {
972 zval *zparams[2] = {NULL, NULL}, retval;
973 int result = 0;
974
975 MAKE_STD_ZVAL(zparams[0]);
976 MAKE_STD_ZVAL(zparams[1]);
977 ZVAL_STRING(zparams[0], user, 0);
978 ZVAL_STRING(zparams[1], pass, 0);
979
980 if (SUCCESS == call_user_function(EG(function_table), NULL, cb,
981 &retval, 2, zparams TSRMLS_CC)) {
982 result = Z_LVAL(retval);
983 }
984
985 efree(user);
986 efree(pass);
987 efree(zparams[0]);
988 efree(zparams[1]);
989
990 if (!result) {
991 http_auth_header("Basic", realm);
992 }
993
994 RETURN_BOOL(result);
995 }
996 }
997 /* }}}*/
998
999
1000 /* {{{ php_http_init_globals(zend_http_globals *) */
1001 static void php_http_init_globals(zend_http_globals *http_globals)
1002 {
1003 http_globals->etag_started = 0;
1004 http_globals->ctype = NULL;
1005 http_globals->etag = NULL;
1006 http_globals->lmod = 0;
1007 #ifdef HTTP_HAVE_CURL
1008 http_globals->curlbuf.body.data = NULL;
1009 http_globals->curlbuf.body.used = 0;
1010 http_globals->curlbuf.body.free = 0;
1011 http_globals->curlbuf.hdrs.data = NULL;
1012 http_globals->curlbuf.hdrs.used = 0;
1013 http_globals->curlbuf.hdrs.free = 0;
1014 #endif
1015 }
1016 /* }}} */
1017
1018 /* {{{ PHP_MINIT_FUNCTION */
1019 PHP_MINIT_FUNCTION(http)
1020 {
1021 ZEND_INIT_MODULE_GLOBALS(http, php_http_init_globals, NULL);
1022 #ifdef HTTP_HAVE_CURL
1023 REGISTER_LONG_CONSTANT("HTTP_AUTH_BASIC", CURLAUTH_BASIC, CONST_CS | CONST_PERSISTENT);
1024 REGISTER_LONG_CONSTANT("HTTP_AUTH_DIGEST", CURLAUTH_DIGEST, CONST_CS | CONST_PERSISTENT);
1025 REGISTER_LONG_CONSTANT("HTTP_AUTH_NTLM", CURLAUTH_NTLM, CONST_CS | CONST_PERSISTENT);
1026 #endif
1027 return SUCCESS;
1028 }
1029 /* }}} */
1030
1031 /* {{{ PHP_RSHUTDOWN_FUNCTION */
1032 PHP_RSHUTDOWN_FUNCTION(http)
1033 {
1034 if (HTTP_G(ctype)) {
1035 efree(HTTP_G(ctype));
1036 }
1037 if (HTTP_G(etag)) {
1038 efree(HTTP_G(etag));
1039 }
1040 #ifdef HTTP_HAVE_CURL
1041 if (HTTP_G(curlbuf).body.data) {
1042 efree(HTTP_G(curlbuf).body.data);
1043 }
1044 if (HTTP_G(curlbuf).hdrs.data) {
1045 efree(HTTP_G(curlbuf).hdrs.data);
1046 }
1047 #endif
1048 return SUCCESS;
1049 }
1050 /* }}} */
1051
1052 /* {{{ PHP_MINFO_FUNCTION */
1053 PHP_MINFO_FUNCTION(http)
1054 {
1055 php_info_print_table_start();
1056 php_info_print_table_header(2, "Extended HTTP support", "enabled");
1057 php_info_print_table_row(2, "Version:", PHP_EXT_HTTP_VERSION);
1058 php_info_print_table_row(2, "cURL convenience functions:",
1059 #ifdef HTTP_HAVE_CURL
1060 "enabled"
1061 #else
1062 "disabled"
1063 #endif
1064 );
1065 php_info_print_table_end();
1066 }
1067 /* }}} */
1068
1069 /*
1070 * Local variables:
1071 * tab-width: 4
1072 * c-basic-offset: 4
1073 * End:
1074 * vim600: noet sw=4 ts=4 fdm=marker
1075 * vim<600: noet sw=4 ts=4
1076 */
1077