2 +--------------------------------------------------------------------+
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-2007, Michael Wallner <mike@php.net> |
10 +--------------------------------------------------------------------+
15 #define HTTP_WANT_SAPI
16 #define HTTP_WANT_CURL
17 #define HTTP_WANT_ZLIB
20 #include "php_http_api.h"
21 #include "php_http_encoding_api.h"
22 #include "php_http_headers_api.h"
23 #include "php_http_message_api.h"
24 #include "php_http_request_api.h"
25 #include "php_http_send_api.h"
26 #include "php_http_url_api.h"
28 #define http_message_info_callback _http_message_info_callback
29 static void _http_message_info_callback(http_message
**message
, HashTable
**headers
, http_info
*info TSRMLS_DC
)
31 http_message
*old
= *message
;
34 if (old
->type
|| zend_hash_num_elements(&old
->hdrs
) || PHPSTR_LEN(old
)) {
35 (*message
) = http_message_new();
36 (*message
)->parent
= old
;
37 (*headers
) = &((*message
)->hdrs
);
40 http_message_set_info(*message
, info
);
43 #define http_message_init_type _http_message_init_type
44 static inline void _http_message_init_type(http_message
*message
, http_message_type type
)
46 message
->http
.version
= .0;
48 switch (message
->type
= type
) {
49 case HTTP_MSG_RESPONSE
:
50 message
->http
.info
.response
.code
= 0;
51 message
->http
.info
.response
.status
= NULL
;
54 case HTTP_MSG_REQUEST
:
55 message
->http
.info
.request
.method
= NULL
;
56 message
->http
.info
.request
.url
= NULL
;
65 PHP_HTTP_API http_message
*_http_message_init_ex(http_message
*message
, http_message_type type ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC
)
68 message
= ecalloc_rel(1, sizeof(http_message
));
71 http_message_init_type(message
, type
);
72 message
->parent
= NULL
;
73 phpstr_init(&message
->body
);
74 zend_hash_init(&message
->hdrs
, 0, NULL
, ZVAL_PTR_DTOR
, 0);
79 PHP_HTTP_API http_message
*_http_message_init_env(http_message
*message
, http_message_type type TSRMLS_DC ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC
)
87 if ((free_msg
= !message
)) {
88 message
= http_message_init_rel(NULL
, HTTP_MSG_NONE
);
91 memset(&inf
, 0, sizeof(http_info
));
92 switch (inf
.type
= type
) {
93 case HTTP_MSG_REQUEST
:
94 if ((sval
= http_get_server_var("SERVER_PROTOCOL", 1)) && !strncmp(Z_STRVAL_P(sval
), "HTTP/", lenof("HTTP/"))) {
95 inf
.http
.version
= zend_strtod(Z_STRVAL_P(sval
) + lenof("HTTP/"), NULL
);
97 inf
.http
.version
= 1.1;
99 if ((sval
= http_get_server_var("REQUEST_METHOD", 1))) {
100 inf
.http
.info
.request
.method
= estrdup(Z_STRVAL_P(sval
));
102 if ((sval
= http_get_server_var("REQUEST_URI", 1))) {
103 inf
.http
.info
.request
.url
= estrdup(Z_STRVAL_P(sval
));
106 http_message_set_info(message
, &inf
);
107 http_get_request_headers(&message
->hdrs
);
108 if (SUCCESS
== http_get_request_body_ex(&body_str
, &body_len
, 0)) {
109 phpstr_from_string_ex(&message
->body
, body_str
, body_len
);
113 case HTTP_MSG_RESPONSE
:
114 if (!SG(sapi_headers
).http_status_line
|| SUCCESS
!= http_info_parse_ex(SG(sapi_headers
).http_status_line
, &inf
, 0)) {
115 inf
.http
.version
= 1.1;
116 inf
.http
.info
.response
.code
= 200;
117 inf
.http
.info
.response
.status
= estrdup("Ok");
120 http_message_set_info(message
, &inf
);
121 http_get_response_headers(&message
->hdrs
);
122 if (SUCCESS
== php_ob_get_buffer(&tval TSRMLS_CC
)) {
123 message
->body
.data
= Z_STRVAL(tval
);
124 message
->body
.used
= Z_STRLEN(tval
);
125 message
->body
.free
= 1; /* "\0" */
131 http_message_free(&message
);
137 http_info_dtor(&inf
);
142 PHP_HTTP_API
void _http_message_set_type(http_message
*message
, http_message_type type
)
144 /* just act if different */
145 if (type
!= message
->type
) {
147 /* free request info */
148 switch (message
->type
) {
149 case HTTP_MSG_REQUEST
:
150 STR_FREE(message
->http
.info
.request
.method
);
151 STR_FREE(message
->http
.info
.request
.url
);
154 case HTTP_MSG_RESPONSE
:
155 STR_FREE(message
->http
.info
.response
.status
);
163 http_message_init_type(message
, type
);
167 PHP_HTTP_API
void _http_message_set_info(http_message
*message
, http_info
*info
)
169 http_message_set_type(message
, info
->type
);
170 message
->http
.version
= info
->http
.version
;
171 switch (message
->type
) {
172 case IS_HTTP_REQUEST
:
173 STR_SET(HTTP_INFO(message
).request
.url
, HTTP_INFO(info
).request
.url
? estrdup(HTTP_INFO(info
).request
.url
) : NULL
);
174 STR_SET(HTTP_INFO(message
).request
.method
, HTTP_INFO(info
).request
.method
? estrdup(HTTP_INFO(info
).request
.method
) : NULL
);
177 case IS_HTTP_RESPONSE
:
178 HTTP_INFO(message
).response
.code
= HTTP_INFO(info
).response
.code
;
179 STR_SET(HTTP_INFO(message
).response
.status
, HTTP_INFO(info
).response
.status
? estrdup(HTTP_INFO(info
).response
.status
) : NULL
);
187 #define http_message_body_parse(m, ms, ml, c) _http_message_body_parse((m), (ms), (ml), (c) TSRMLS_CC)
188 static inline void _http_message_body_parse(http_message
*msg
, const char *message
, size_t message_length
, const char **continue_at TSRMLS_DC
)
195 if ((body
= http_locate_body(message
))) {
196 remaining
= message
+ message_length
- body
;
198 if ((c
= http_message_header(msg
, "Transfer-Encoding"))) {
199 if (strstr(Z_STRVAL_P(c
), "chunked")) {
200 /* message has chunked transfer encoding */
204 /* decode and replace Transfer-Encoding with Content-Length header */
205 if ((*continue_at
= http_encoding_dechunk(body
, message
+ message_length
- body
, &decoded
, &decoded_len
))) {
210 tmp_len
= (int) spprintf(&tmp
, 0, "%zu", decoded_len
);
212 ZVAL_STRINGL(len
, tmp
, tmp_len
, 0);
215 zend_hash_update(&msg
->hdrs
, "X-Original-Transfer-Encoding", sizeof("X-Original-Transfer-Encoding"), (void *) &c
, sizeof(zval
*), NULL
);
216 zend_hash_del(&msg
->hdrs
, "Transfer-Encoding", sizeof("Transfer-Encoding"));
217 zend_hash_del(&msg
->hdrs
, "Content-Length", sizeof("Content-Length"));
218 zend_hash_update(&msg
->hdrs
, "Content-Length", sizeof("Content-Length"), (void *) &len
, sizeof(zval
*), NULL
);
220 phpstr_from_string_ex(PHPSTR(msg
), decoded
, decoded_len
);
227 if (!*continue_at
&& (c
= http_message_header(msg
, "Content-Length"))) {
228 /* message has content-length header */
229 ulong len
= strtoul(Z_STRVAL_P(c
), NULL
, 10);
230 if (len
> remaining
) {
231 http_error_ex(HE_NOTICE
, HTTP_E_MALFORMED_HEADERS
, "The Content-Length header pretends a larger body than actually received (expected %lu bytes; got %lu bytes)", len
, remaining
);
234 phpstr_from_string_ex(PHPSTR(msg
), body
, len
);
235 *continue_at
= body
+ len
;
239 if (!*continue_at
&& (c
= http_message_header(msg
, "Content-Range"))) {
240 /* message has content-range header */
241 ulong total
= 0, start
= 0, end
= 0, len
= 0;
243 if (!strncasecmp(Z_STRVAL_P(c
), "bytes", lenof("bytes")) &&
244 ( Z_STRVAL_P(c
)[lenof("bytes")] == ':' ||
245 Z_STRVAL_P(c
)[lenof("bytes")] == ' ' ||
246 Z_STRVAL_P(c
)[lenof("bytes")] == '=')) {
247 char *total_at
= NULL
, *end_at
= NULL
;
248 char *start_at
= Z_STRVAL_P(c
) + sizeof("bytes");
250 start
= strtoul(start_at
, &end_at
, 10);
252 end
= strtoul(end_at
+ 1, &total_at
, 10);
253 if (total_at
&& strncmp(total_at
+ 1, "*", 1)) {
254 total
= strtoul(total_at
+ 1, NULL
, 10);
256 if ((len
= (end
+ 1 - start
)) > remaining
) {
257 http_error_ex(HE_NOTICE
, HTTP_E_MALFORMED_HEADERS
, "The Content-Range header pretends a larger body than actually received (expected %lu bytes; got %lu bytes)", len
, remaining
);
260 if (end
>= start
&& (!total
|| end
< total
)) {
261 phpstr_from_string_ex(PHPSTR(msg
), body
, len
);
262 *continue_at
= body
+ len
;
268 http_error_ex(HE_WARNING
, HTTP_E_MALFORMED_HEADERS
, "Invalid Content-Range header: %s", Z_STRVAL_P(c
));
274 /* no headers that indicate content length */
275 if (HTTP_MSG_TYPE(RESPONSE
, msg
)) {
276 phpstr_from_string_ex(PHPSTR(msg
), body
, remaining
);
282 #ifdef HTTP_HAVE_ZLIB
283 /* check for compressed data */
284 if ((c
= http_message_header(msg
, "Vary"))) {
287 if ((c
= http_message_header(msg
, "Content-Encoding"))) {
288 char *decoded
= NULL
;
289 size_t decoded_len
= 0;
291 if ( !strcasecmp(Z_STRVAL_P(c
), "gzip") ||
292 !strcasecmp(Z_STRVAL_P(c
), "x-gzip") ||
293 !strcasecmp(Z_STRVAL_P(c
), "deflate")) {
294 http_encoding_inflate(PHPSTR_VAL(msg
), PHPSTR_LEN(msg
), &decoded
, &decoded_len
);
298 zval
*len
, **original_len
;
302 tmp_len
= (int) spprintf(&tmp
, 0, "%zu", decoded_len
);
304 ZVAL_STRINGL(len
, tmp
, tmp_len
, 0);
307 zend_hash_update(&msg
->hdrs
, "X-Original-Content-Encoding", sizeof("X-Original-Content-Encoding"), (void *) &c
, sizeof(zval
*), NULL
);
308 zend_hash_del(&msg
->hdrs
, "Content-Encoding", sizeof("Content-Encoding"));
309 if (SUCCESS
== zend_hash_find(&msg
->hdrs
, "Content-Length", sizeof("Content-Length"), (void *) &original_len
)) {
310 ZVAL_ADDREF(*original_len
);
311 zend_hash_update(&msg
->hdrs
, "X-Original-Content-Length", sizeof("X-Original-Content-Length"), (void *) original_len
, sizeof(zval
*), NULL
);
312 zend_hash_update(&msg
->hdrs
, "Content-Length", sizeof("Content-Length"), (void *) &len
, sizeof(zval
*), NULL
);
314 zend_hash_update(&msg
->hdrs
, "Content-Length", sizeof("Content-Length"), (void *) &len
, sizeof(zval
*), NULL
);
317 phpstr_dtor(PHPSTR(msg
));
318 PHPSTR(msg
)->data
= decoded
;
319 PHPSTR(msg
)->used
= decoded_len
;
320 PHPSTR(msg
)->free
= 1;
326 #endif /* HTTP_HAVE_ZLIB */
330 PHP_HTTP_API http_message
*_http_message_parse_ex(http_message
*msg
, const char *message
, size_t message_length ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC TSRMLS_DC
)
332 const char *continue_at
;
333 zend_bool free_msg
= msg
? 0 : 1;
335 if ((!message
) || (message_length
< HTTP_MSG_MIN_SIZE
)) {
336 http_error_ex(HE_WARNING
, HTTP_E_INVALID_PARAM
, "Empty or too short HTTP message: '%s'", message
);
340 msg
= http_message_init_rel(msg
, 0);
342 if (SUCCESS
!= http_parse_headers_cb(message
, &msg
->hdrs
, 1, (http_info_callback
) http_message_info_callback
, (void *) &msg
)) {
344 http_message_free(&msg
);
346 http_error(HE_WARNING
, HTTP_E_MALFORMED_HEADERS
, "Failed to parse message headers");
350 http_message_body_parse(msg
, message
, message_length
, &continue_at
);
352 /* check for following messages */
353 if (continue_at
&& (continue_at
< (message
+ message_length
))) {
354 while (HTTP_IS_CTYPE(space
, *continue_at
)) ++continue_at
;
355 if (continue_at
< (message
+ message_length
)) {
356 http_message
*next
= NULL
, *most
= NULL
;
358 /* set current message to parent of most parent following messages and return deepest */
359 if ((most
= next
= http_message_parse_rel(NULL
, continue_at
, message
+ message_length
- continue_at
))) {
360 while (most
->parent
) most
= most
->parent
;
370 PHP_HTTP_API
void _http_message_tostring(http_message
*msg
, char **string
, size_t *length
)
373 HashKey key
= initHashKey(0);
378 phpstr_init_ex(&str
, 4096, 0);
381 case HTTP_MSG_REQUEST
:
382 phpstr_appendf(&str
, HTTP_INFO_REQUEST_FMT_ARGS(&msg
->http
, HTTP_CRLF
));
385 case HTTP_MSG_RESPONSE
:
386 phpstr_appendf(&str
, HTTP_INFO_RESPONSE_FMT_ARGS(&msg
->http
, HTTP_CRLF
));
394 FOREACH_HASH_KEYVAL(pos1
, &msg
->hdrs
, key
, header
) {
395 if (key
.type
== HASH_KEY_IS_STRING
) {
397 zval
**single_header
;
399 switch (Z_TYPE_PP(header
)) {
401 phpstr_appendf(&str
, "%s: %s" HTTP_CRLF
, key
.str
, Z_BVAL_PP(header
)?"true":"false");
405 phpstr_appendf(&str
, "%s: %ld" HTTP_CRLF
, key
.str
, Z_LVAL_PP(header
));
409 phpstr_appendf(&str
, "%s: %f" HTTP_CRLF
, key
.str
, Z_DVAL_PP(header
));
413 phpstr_appendf(&str
, "%s: %s" HTTP_CRLF
, key
.str
, Z_STRVAL_PP(header
));
417 FOREACH_VAL(pos2
, *header
, single_header
) {
418 switch (Z_TYPE_PP(single_header
)) {
420 phpstr_appendf(&str
, "%s: %s" HTTP_CRLF
, key
.str
, Z_BVAL_PP(single_header
)?"true":"false");
424 phpstr_appendf(&str
, "%s: %ld" HTTP_CRLF
, key
.str
, Z_LVAL_PP(single_header
));
428 phpstr_appendf(&str
, "%s: %f" HTTP_CRLF
, key
.str
, Z_DVAL_PP(single_header
));
432 phpstr_appendf(&str
, "%s: %s" HTTP_CRLF
, key
.str
, Z_STRVAL_PP(single_header
));
441 if (PHPSTR_LEN(msg
)) {
442 phpstr_appends(&str
, HTTP_CRLF
);
443 phpstr_append(&str
, PHPSTR_VAL(msg
), PHPSTR_LEN(msg
));
444 phpstr_appends(&str
, HTTP_CRLF
);
447 data
= phpstr_data(&str
, string
, length
);
455 PHP_HTTP_API
void _http_message_serialize(http_message
*message
, char **string
, size_t *length
)
464 http_message_tostring(message
, &buf
, &len
);
465 phpstr_prepend(&str
, buf
, len
);
467 } while ((message
= message
->parent
));
469 buf
= phpstr_data(&str
, string
, length
);
477 PHP_HTTP_API http_message
*_http_message_reverse(http_message
*msg
)
481 http_message_count(c
, msg
);
484 http_message
*tmp
= msg
, **arr
= ecalloc(c
, sizeof(http_message
*));
486 for (i
= 0; i
< c
; ++i
) {
490 arr
[0]->parent
= NULL
;
491 for (i
= 0; i
< c
-1; ++i
) {
492 arr
[i
+1]->parent
= arr
[i
];
502 PHP_HTTP_API http_message
*_http_message_interconnect(http_message
*m1
, http_message
*m2
)
506 http_message
*t1
= m1
, *t2
= m2
, *p1
, *p2
;
508 http_message_count(c1
, m1
);
509 http_message_count(c2
, m2
);
511 while (i
++ < (c1
- c2
)) {
522 } else if (!m1
&& m2
) {
528 PHP_HTTP_API
void _http_message_tostruct_recursive(http_message
*msg
, zval
*obj TSRMLS_DC
)
533 INIT_ZARR(strct
, HASH_OF(obj
));
535 add_assoc_long(&strct
, "type", msg
->type
);
536 add_assoc_double(&strct
, "httpVersion", msg
->http
.version
);
539 case HTTP_MSG_RESPONSE
:
540 add_assoc_long(&strct
, "responseCode", msg
->http
.info
.response
.code
);
541 add_assoc_string(&strct
, "responseStatus", STR_PTR(msg
->http
.info
.response
.status
), 1);
544 case HTTP_MSG_REQUEST
:
545 add_assoc_string(&strct
, "requestMethod", STR_PTR(msg
->http
.info
.request
.method
), 1);
546 add_assoc_string(&strct
, "requestUrl", STR_PTR(msg
->http
.info
.request
.url
), 1);
550 /* avoid compiler warning */
554 MAKE_STD_ZVAL(headers
);
556 zend_hash_copy(Z_ARRVAL_P(headers
), &msg
->hdrs
, (copy_ctor_func_t
) zval_add_ref
, NULL
, sizeof(zval
*));
557 add_assoc_zval(&strct
, "headers", headers
);
559 add_assoc_stringl(&strct
, "body", PHPSTR_VAL(msg
), PHPSTR_LEN(msg
), 1);
564 MAKE_STD_ZVAL(parent
);
565 if (Z_TYPE_P(obj
) == IS_ARRAY
) {
570 add_assoc_zval(&strct
, "parentMessage", parent
);
571 http_message_tostruct_recursive(msg
->parent
, parent
);
573 add_assoc_null(&strct
, "parentMessage");
577 PHP_HTTP_API STATUS
_http_message_send(http_message
*message TSRMLS_DC
)
581 switch (message
->type
) {
582 case HTTP_MSG_RESPONSE
:
584 HashKey key
= initHashKey(0);
588 FOREACH_HASH_KEYVAL(pos
, &message
->hdrs
, key
, val
) {
589 if (key
.type
== HASH_KEY_IS_STRING
) {
590 http_send_header_zval_ex(key
.str
, key
.len
-1, val
, 1);
593 rs
= SUCCESS
== http_send_status(message
->http
.info
.response
.code
) &&
594 SUCCESS
== http_send_data(PHPSTR_VAL(message
), PHPSTR_LEN(message
)) ?
599 case HTTP_MSG_REQUEST
:
601 #ifdef HTTP_HAVE_CURL
603 http_request request
;
604 zval
**zhost
, *options
, *headers
;
606 MAKE_STD_ZVAL(options
);
607 MAKE_STD_ZVAL(headers
);
610 zend_hash_copy(Z_ARRVAL_P(headers
), &message
->hdrs
, (copy_ctor_func_t
) zval_add_ref
, NULL
, sizeof(zval
*));
611 add_assoc_zval(options
, "headers", headers
);
613 /* check host header */
614 if (SUCCESS
== zend_hash_find(&message
->hdrs
, "Host", sizeof("Host"), (void *) &zhost
) && Z_TYPE_PP(zhost
) == IS_STRING
) {
616 php_url parts
, *url
= php_url_parse(message
->http
.info
.request
.url
);
618 memset(&parts
, 0, sizeof(php_url
));
621 if ((colon
= strchr(Z_STRVAL_PP(zhost
), ':'))) {
622 parts
.port
= atoi(colon
+ 1);
623 parts
.host
= estrndup(Z_STRVAL_PP(zhost
), (Z_STRVAL_PP(zhost
) - colon
- 1));
625 parts
.host
= estrndup(Z_STRVAL_PP(zhost
), Z_STRLEN_PP(zhost
));
628 http_build_url(HTTP_URL_REPLACE
, url
, &parts
, NULL
, &uri
, NULL
);
632 uri
= http_absolute_url(message
->http
.info
.request
.url
);
635 if ((request
.meth
= http_request_method_exists(1, 0, message
->http
.info
.request
.method
))) {
636 http_request_body body
;
638 http_request_init_ex(&request
, NULL
, request
.meth
, uri
);
639 request
.body
= http_request_body_init_ex(&body
, HTTP_REQUEST_BODY_CSTRING
, PHPSTR_VAL(message
), PHPSTR_LEN(message
), 0);
640 if (SUCCESS
== (rs
= http_request_prepare(&request
, Z_ARRVAL_P(options
)))) {
641 http_request_exec(&request
);
643 http_request_dtor(&request
);
645 http_error_ex(HE_WARNING
, HTTP_E_REQUEST_METHOD
,
646 "Cannot send HttpMessage. Request method %s not supported",
647 message
->http
.info
.request
.method
);
650 zval_ptr_dtor(&options
);
652 http_error(HE_WARNING
, HTTP_E_RUNTIME
, "HTTP requests not supported - ext/http was not linked against libcurl.");
659 http_error(HE_WARNING
, HTTP_E_MESSAGE_TYPE
, "HttpMessage is neither of type HTTP_MSG_REQUEST nor HTTP_MSG_RESPONSE");
666 PHP_HTTP_API http_message
*_http_message_dup(http_message
*orig TSRMLS_DC
)
668 http_message
*temp
, *copy
= NULL
;
672 info
.type
= orig
->type
;
673 info
.http
= orig
->http
;
675 copy
= temp
= http_message_new();
676 http_message_set_info(temp
, &info
);
677 zend_hash_copy(&temp
->hdrs
, &orig
->hdrs
, (copy_ctor_func_t
) zval_add_ref
, NULL
, sizeof(zval
*));
678 phpstr_append(&temp
->body
, orig
->body
.data
, orig
->body
.used
);
680 while (orig
->parent
) {
681 info
.type
= orig
->parent
->type
;
682 info
.http
= orig
->parent
->http
;
684 temp
->parent
= http_message_new();
685 http_message_set_info(temp
->parent
, &info
);
686 zend_hash_copy(&temp
->parent
->hdrs
, &orig
->parent
->hdrs
, (copy_ctor_func_t
) zval_add_ref
, NULL
, sizeof(zval
*));
687 phpstr_append(&temp
->parent
->body
, orig
->parent
->body
.data
, orig
->parent
->body
.used
);
697 PHP_HTTP_API
void _http_message_dtor(http_message
*message
)
700 zend_hash_destroy(&message
->hdrs
);
701 phpstr_dtor(PHPSTR(message
));
703 switch (message
->type
) {
704 case HTTP_MSG_REQUEST
:
705 STR_SET(message
->http
.info
.request
.method
, NULL
);
706 STR_SET(message
->http
.info
.request
.url
, NULL
);
709 case HTTP_MSG_RESPONSE
:
710 STR_SET(message
->http
.info
.response
.status
, NULL
);
719 PHP_HTTP_API
void _http_message_free(http_message
**message
)
722 if ((*message
)->parent
) {
723 http_message_free(&(*message
)->parent
);
725 http_message_dtor(*message
);
736 * vim600: noet sw=4 ts=4 fdm=marker
737 * vim<600: noet sw=4 ts=4