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-2006, Michael Wallner <mike@php.net> |
10 +--------------------------------------------------------------------+
19 #define HTTP_WANT_CURL
20 #define HTTP_WANT_ZLIB
25 #include "php_http_api.h"
26 #include "php_http_encoding_api.h"
27 #include "php_http_headers_api.h"
28 #include "php_http_message_api.h"
29 #include "php_http_request_api.h"
30 #include "php_http_send_api.h"
31 #include "php_http_url_api.h"
33 #define http_message_info_callback _http_message_info_callback
34 static void _http_message_info_callback(http_message
**message
, HashTable
**headers
, http_info
*info TSRMLS_DC
)
36 http_message
*old
= *message
;
39 if (old
->type
|| zend_hash_num_elements(&old
->hdrs
) || PHPSTR_LEN(old
)) {
40 (*message
) = http_message_new();
41 (*message
)->parent
= old
;
42 (*headers
) = &((*message
)->hdrs
);
45 (*message
)->http
.version
= info
->http
.version
;
50 (*message
)->type
= HTTP_MSG_REQUEST
;
51 HTTP_INFO(*message
).request
.url
= estrdup(HTTP_INFO(info
).request
.url
);
52 HTTP_INFO(*message
).request
.method
= estrdup(HTTP_INFO(info
).request
.method
);
55 case IS_HTTP_RESPONSE
:
56 (*message
)->type
= HTTP_MSG_RESPONSE
;
57 HTTP_INFO(*message
).response
.code
= HTTP_INFO(info
).response
.code
;
58 HTTP_INFO(*message
).response
.status
= estrdup(HTTP_INFO(info
).response
.status
);
63 #define http_message_init_type _http_message_init_type
64 static inline void _http_message_init_type(http_message
*message
, http_message_type type
)
66 message
->http
.version
= .0;
68 switch (message
->type
= type
)
70 case HTTP_MSG_RESPONSE
:
71 message
->http
.info
.response
.code
= 0;
72 message
->http
.info
.response
.status
= NULL
;
75 case HTTP_MSG_REQUEST
:
76 message
->http
.info
.request
.method
= NULL
;
77 message
->http
.info
.request
.url
= NULL
;
86 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
)
89 message
= ecalloc_rel(1, sizeof(http_message
));
92 http_message_init_type(message
, type
);
93 message
->parent
= NULL
;
94 phpstr_init(&message
->body
);
95 zend_hash_init(&message
->hdrs
, 0, NULL
, ZVAL_PTR_DTOR
, 0);
101 PHP_HTTP_API
void _http_message_set_type(http_message
*message
, http_message_type type
)
103 /* just act if different */
104 if (type
!= message
->type
) {
106 /* free request info */
107 switch (message
->type
)
109 case HTTP_MSG_REQUEST
:
110 STR_FREE(message
->http
.info
.request
.method
);
111 STR_FREE(message
->http
.info
.request
.url
);
114 case HTTP_MSG_RESPONSE
:
115 STR_FREE(message
->http
.info
.response
.status
);
123 http_message_init_type(message
, type
);
127 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
)
129 const char *body
= NULL
;
130 zend_bool free_msg
= msg
? 0 : 1;
132 if ((!message
) || (message_length
< HTTP_MSG_MIN_SIZE
)) {
133 http_error_ex(HE_WARNING
, HTTP_E_INVALID_PARAM
, "Empty or too short HTTP message: '%s'", message
);
137 msg
= http_message_init_rel(msg
, 0);
139 if (SUCCESS
!= http_parse_headers_cb(message
, &msg
->hdrs
, 1, (http_info_callback
) http_message_info_callback
, (void **) &msg
)) {
141 http_message_free(&msg
);
143 http_error(HE_WARNING
, HTTP_E_MALFORMED_HEADERS
, "Failed to parse message headers");
147 /* header parsing stops at (CR)LF (CR)LF */
148 if ((body
= http_locate_body(message
))) {
150 const char *continue_at
= NULL
;
151 size_t remaining
= message
+ message_length
- body
;
153 /* message has chunked transfer encoding */
154 if ((c
= http_message_header(msg
, "Transfer-Encoding")) && (!strcasecmp("chunked", Z_STRVAL_P(c
)))) {
158 /* decode and replace Transfer-Encoding with Content-Length header */
159 if ((continue_at
= http_encoding_dechunk(body
, message
+ message_length
- body
, &decoded
, &decoded_len
))) {
164 tmp_len
= (int) spprintf(&tmp
, 0, "%zu", decoded_len
);
166 ZVAL_STRINGL(len
, tmp
, tmp_len
, 0);
169 zend_hash_add(&msg
->hdrs
, "X-Original-Transfer-Encoding", sizeof("X-Original-Transfer-Encoding"), (void *) &c
, sizeof(zval
*), NULL
);
170 zend_hash_del(&msg
->hdrs
, "Transfer-Encoding", sizeof("Transfer-Encoding"));
171 zend_hash_del(&msg
->hdrs
, "Content-Length", sizeof("Content-Length"));
172 zend_hash_add(&msg
->hdrs
, "Content-Length", sizeof("Content-Length"), (void *) &len
, sizeof(zval
*), NULL
);
174 phpstr_from_string_ex(PHPSTR(msg
), decoded
, decoded_len
);
179 /* message has content-length header */
180 if ((c
= http_message_header(msg
, "Content-Length"))) {
181 ulong len
= strtoul(Z_STRVAL_P(c
), NULL
, 10);
182 if (len
> remaining
) {
183 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
);
186 phpstr_from_string_ex(PHPSTR(msg
), body
, len
);
187 continue_at
= body
+ len
;
190 /* message has content-range header */
191 if ((c
= http_message_header(msg
, "Content-Range"))) {
192 ulong total
= 0, start
= 0, end
= 0, len
= 0;
194 if (!strncasecmp(Z_STRVAL_P(c
), "bytes", lenof("bytes")) &&
195 (Z_STRVAL_P(c
)[lenof("bytes")] == ':' || Z_STRVAL_P(c
)[lenof("bytes")] == ' ')) {
196 char *total_at
= NULL
, *end_at
= NULL
;
197 char *start_at
= Z_STRVAL_P(c
) + sizeof("bytes");
199 start
= strtoul(start_at
, &end_at
, 10);
201 end
= strtoul(end_at
+ 1, &total_at
, 10);
202 if (total_at
&& strncmp(total_at
+ 1, "*", 1)) {
203 total
= strtoul(total_at
+ 1, NULL
, 10);
205 if ((len
= (end
+ 1 - start
)) > remaining
) {
206 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
);
209 if (end
>= start
&& (!total
|| end
< total
)) {
210 phpstr_from_string_ex(PHPSTR(msg
), body
, len
);
211 continue_at
= body
+ len
;
217 http_error_ex(HE_WARNING
, HTTP_E_MALFORMED_HEADERS
, "Invalid Content-Range header: %s", Z_STRVAL_P(c
));
221 /* no headers that indicate content length */
222 if (HTTP_MSG_TYPE(RESPONSE
, msg
)) {
223 phpstr_from_string_ex(PHPSTR(msg
), body
, remaining
);
228 #ifdef HTTP_HAVE_ZLIB
229 /* check for compressed data */
230 if (http_message_header(msg
, "Vary") && (c
= http_message_header(msg
, "Content-Encoding"))) {
231 char *decoded
= NULL
;
232 size_t decoded_len
= 0;
234 if ( !strcasecmp(Z_STRVAL_P(c
), "gzip") ||
235 !strcasecmp(Z_STRVAL_P(c
), "x-gzip") ||
236 !strcasecmp(Z_STRVAL_P(c
), "deflate")) {
237 http_encoding_inflate(PHPSTR_VAL(msg
), PHPSTR_LEN(msg
), &decoded
, &decoded_len
);
241 zval
*len
, **original_len
;
245 tmp_len
= (int) spprintf(&tmp
, 0, "%zu", decoded_len
);
247 ZVAL_STRINGL(len
, tmp
, tmp_len
, 0);
250 zend_hash_add(&msg
->hdrs
, "X-Original-Content-Encoding", sizeof("X-Original-Content-Encoding"), (void *) &c
, sizeof(zval
*), NULL
);
251 zend_hash_del(&msg
->hdrs
, "Content-Encoding", sizeof("Content-Encoding"));
252 if (SUCCESS
== zend_hash_find(&msg
->hdrs
, "Content-Length", sizeof("Content-Length"), (void **) &original_len
)) {
253 ZVAL_ADDREF(*original_len
);
254 zend_hash_add(&msg
->hdrs
, "X-Original-Content-Length", sizeof("X-Original-Content-Length"), (void *) original_len
, sizeof(zval
*), NULL
);
255 zend_hash_update(&msg
->hdrs
, "Content-Length", sizeof("Content-Length"), (void *) &len
, sizeof(zval
*), NULL
);
257 zend_hash_add(&msg
->hdrs
, "Content-Length", sizeof("Content-Length"), (void *) &len
, sizeof(zval
*), NULL
);
260 phpstr_dtor(PHPSTR(msg
));
261 PHPSTR(msg
)->data
= decoded
;
262 PHPSTR(msg
)->used
= decoded_len
;
263 PHPSTR(msg
)->free
= 1;
266 #endif /* HTTP_HAVE_ZLIB */
268 /* check for following messages */
269 if (continue_at
&& (continue_at
< (message
+ message_length
))) {
270 while (isspace(*continue_at
)) ++continue_at
;
271 if (continue_at
< (message
+ message_length
)) {
272 http_message
*next
= NULL
, *most
= NULL
;
274 /* set current message to parent of most parent following messages and return deepest */
275 if ((most
= next
= http_message_parse_rel(NULL
, continue_at
, message
+ message_length
- continue_at
))) {
276 while (most
->parent
) most
= most
->parent
;
287 PHP_HTTP_API
void _http_message_tostring(http_message
*msg
, char **string
, size_t *length
)
295 phpstr_init_ex(&str
, 4096, 0);
299 case HTTP_MSG_REQUEST
:
300 phpstr_appendf(&str
, "%s %s HTTP/%1.1f" HTTP_CRLF
,
301 msg
->http
.info
.request
.method
,
302 msg
->http
.info
.request
.url
,
306 case HTTP_MSG_RESPONSE
:
307 phpstr_appendf(&str
, "HTTP/%1.1f %d%s%s" HTTP_CRLF
,
309 msg
->http
.info
.response
.code
,
310 *msg
->http
.info
.response
.status
? " ":"",
311 msg
->http
.info
.response
.status
);
319 FOREACH_HASH_KEYVAL(pos1
, &msg
->hdrs
, key
, idx
, header
) {
321 zval
**single_header
;
323 switch (Z_TYPE_PP(header
))
326 phpstr_appendf(&str
, "%s: %s" HTTP_CRLF
, key
, Z_STRVAL_PP(header
));
332 FOREACH_VAL(pos2
, *header
, single_header
) {
333 phpstr_appendf(&str
, "%s: %s" HTTP_CRLF
, key
, Z_STRVAL_PP(single_header
));
343 if (PHPSTR_LEN(msg
)) {
344 phpstr_appends(&str
, HTTP_CRLF
);
345 phpstr_append(&str
, PHPSTR_VAL(msg
), PHPSTR_LEN(msg
));
346 phpstr_appends(&str
, HTTP_CRLF
);
349 data
= phpstr_data(&str
, string
, length
);
357 PHP_HTTP_API
void _http_message_serialize(http_message
*message
, char **string
, size_t *length
)
366 http_message_tostring(message
, &buf
, &len
);
367 phpstr_prepend(&str
, buf
, len
);
369 } while ((message
= message
->parent
));
371 buf
= phpstr_data(&str
, string
, length
);
379 PHP_HTTP_API
void _http_message_tostruct_recursive(http_message
*msg
, zval
*obj TSRMLS_DC
)
384 INIT_ZARR(strct
, HASH_OF(obj
));
386 add_assoc_long(&strct
, "type", msg
->type
);
387 add_assoc_double(&strct
, "httpVersion", msg
->http
.version
);
390 case HTTP_MSG_RESPONSE
:
391 add_assoc_long(&strct
, "responseCode", msg
->http
.info
.response
.code
);
392 add_assoc_string(&strct
, "responseStatus", msg
->http
.info
.response
.status
, 1);
395 case HTTP_MSG_REQUEST
:
396 add_assoc_string(&strct
, "requestMethod", msg
->http
.info
.request
.method
, 1);
397 add_assoc_string(&strct
, "requestUrl", msg
->http
.info
.request
.url
, 1);
401 /* avoid compiler warning */
405 MAKE_STD_ZVAL(headers
);
407 zend_hash_copy(Z_ARRVAL_P(headers
), &msg
->hdrs
, (copy_ctor_func_t
) zval_add_ref
, NULL
, sizeof(zval
*));
408 add_assoc_zval(&strct
, "headers", headers
);
410 add_assoc_stringl(&strct
, "body", PHPSTR_VAL(msg
), PHPSTR_LEN(msg
), 1);
415 MAKE_STD_ZVAL(parent
);
416 if (Z_TYPE_P(obj
) == IS_ARRAY
) {
421 add_assoc_zval(&strct
, "parentMessage", parent
);
422 http_message_tostruct_recursive(msg
->parent
, parent
);
424 add_assoc_null(&strct
, "parentMessage");
428 PHP_HTTP_API STATUS
_http_message_send(http_message
*message TSRMLS_DC
)
432 switch (message
->type
)
434 case HTTP_MSG_RESPONSE
:
441 FOREACH_HASH_KEYVAL(pos1
, &message
->hdrs
, key
, idx
, val
) {
443 if (Z_TYPE_PP(val
) == IS_ARRAY
) {
448 FOREACH_VAL(pos2
, *val
, data
) {
449 http_send_header_ex(key
, strlen(key
), Z_STRVAL_PP(data
), Z_STRLEN_PP(data
), first
, NULL
);
453 http_send_header_ex(key
, strlen(key
), Z_STRVAL_PP(val
), Z_STRLEN_PP(val
), 1, NULL
);
458 rs
= SUCCESS
== http_send_status(message
->http
.info
.response
.code
) &&
459 SUCCESS
== http_send_data(PHPSTR_VAL(message
), PHPSTR_LEN(message
)) ?
464 case HTTP_MSG_REQUEST
:
466 #ifdef HTTP_HAVE_CURL
468 http_request request
;
469 zval
**zhost
, options
, headers
;
471 INIT_PZVAL(&options
);
472 INIT_PZVAL(&headers
);
473 array_init(&options
);
474 array_init(&headers
);
475 zend_hash_copy(Z_ARRVAL(headers
), &message
->hdrs
, (copy_ctor_func_t
) zval_add_ref
, NULL
, sizeof(zval
*));
476 add_assoc_zval(&options
, "headers", &headers
);
478 /* check host header */
479 if (SUCCESS
== zend_hash_find(&message
->hdrs
, "Host", sizeof("Host"), (void **) &zhost
)) {
481 php_url parts
, *url
= php_url_parse(message
->http
.info
.request
.url
);
483 memset(&parts
, 0, sizeof(php_url
));
486 if ((colon
= strchr(Z_STRVAL_PP(zhost
), ':'))) {
487 parts
.port
= atoi(colon
+ 1);
488 parts
.host
= estrndup(Z_STRVAL_PP(zhost
), (Z_STRVAL_PP(zhost
) - colon
- 1));
490 parts
.host
= estrndup(Z_STRVAL_PP(zhost
), Z_STRLEN_PP(zhost
));
493 http_build_url(url
, &parts
, NULL
, &uri
, NULL
);
497 uri
= http_absolute_url(message
->http
.info
.request
.url
);
500 if ((request
.meth
= http_request_method_exists(1, 0, message
->http
.info
.request
.method
))) {
501 http_request_body body
;
503 http_request_init_ex(&request
, NULL
, request
.meth
, uri
);
504 request
.body
= http_request_body_init_ex(&body
, HTTP_REQUEST_BODY_CSTRING
, PHPSTR_VAL(message
), PHPSTR_LEN(message
), 0);
505 if (SUCCESS
== (rs
= http_request_prepare(&request
, NULL
))) {
506 http_request_exec(&request
);
508 http_request_dtor(&request
);
510 http_error_ex(HE_WARNING
, HTTP_E_REQUEST_METHOD
,
511 "Cannot send HttpMessage. Request method %s not supported",
512 message
->http
.info
.request
.method
);
516 http_error(HE_WARNING
, HTTP_E_RUNTIME
, "HTTP requests not supported - ext/http was not linked against libcurl.");
523 http_error(HE_WARNING
, HTTP_E_MESSAGE_TYPE
, "HttpMessage is neither of type HTTP_MSG_REQUEST nor HTTP_MSG_RESPONSE");
530 PHP_HTTP_API http_message
*_http_message_dup(http_message
*msg TSRMLS_DC
)
536 char *serialized_data
;
537 size_t serialized_length
;
539 http_message_serialize(msg
, &serialized_data
, &serialized_length
);
540 new = http_message_parse(serialized_data
, serialized_length
);
541 efree(serialized_data
);
545 PHP_HTTP_API
void _http_message_dtor(http_message
*message
)
548 zend_hash_destroy(&message
->hdrs
);
549 phpstr_dtor(PHPSTR(message
));
551 switch (message
->type
)
553 case HTTP_MSG_REQUEST
:
554 STR_SET(message
->http
.info
.request
.method
, NULL
);
555 STR_SET(message
->http
.info
.request
.url
, NULL
);
558 case HTTP_MSG_RESPONSE
:
559 STR_SET(message
->http
.info
.response
.status
, NULL
);
568 PHP_HTTP_API
void _http_message_free(http_message
**message
)
571 if ((*message
)->parent
) {
572 http_message_free(&(*message
)->parent
);
574 http_message_dtor(*message
);
585 * vim600: noet sw=4 ts=4 fdm=marker
586 * vim<600: noet sw=4 ts=4