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-2005, Michael Wallner <mike@php.net> |
10 +--------------------------------------------------------------------+
21 #include "php_http_std_defs.h"
22 #include "php_http_api.h"
23 #include "php_http_message_api.h"
24 #include "php_http_headers_api.h"
25 #include "php_http_send_api.h"
26 #include "php_http_request_api.h"
27 #include "php_http_url_api.h"
28 #include "php_http_encoding_api.h"
30 #include "phpstr/phpstr.h"
32 ZEND_EXTERN_MODULE_GLOBALS(http
);
34 #define http_message_info_callback _http_message_info_callback
35 static void _http_message_info_callback(http_message
**message
, HashTable
**headers
, http_info
*info TSRMLS_DC
)
37 http_message
*old
= *message
;
40 if (old
->type
|| zend_hash_num_elements(&old
->hdrs
) || PHPSTR_LEN(old
)) {
41 (*message
) = http_message_new();
42 (*message
)->parent
= old
;
43 (*headers
) = &((*message
)->hdrs
);
46 (*message
)->http
.version
= info
->http
.version
;
51 (*message
)->type
= HTTP_MSG_REQUEST
;
52 HTTP_INFO(*message
).request
.URI
= estrdup(HTTP_INFO(info
).request
.URI
);
53 HTTP_INFO(*message
).request
.method
= estrdup(HTTP_INFO(info
).request
.method
);
56 case IS_HTTP_RESPONSE
:
57 (*message
)->type
= HTTP_MSG_RESPONSE
;
58 HTTP_INFO(*message
).response
.code
= HTTP_INFO(info
).response
.code
;
59 HTTP_INFO(*message
).response
.status
= estrdup(HTTP_INFO(info
).response
.status
);
64 #define http_message_init_type _http_message_init_type
65 static inline void _http_message_init_type(http_message
*message
, http_message_type type
)
67 message
->http
.version
= .0;
69 switch (message
->type
= type
)
71 case HTTP_MSG_RESPONSE
:
72 message
->http
.info
.response
.code
= 0;
73 message
->http
.info
.response
.status
= NULL
;
76 case HTTP_MSG_REQUEST
:
77 message
->http
.info
.request
.method
= NULL
;
78 message
->http
.info
.request
.URI
= NULL
;
87 PHP_HTTP_API http_message
*_http_message_init_ex(http_message
*message
, http_message_type type
)
90 message
= ecalloc(1, sizeof(http_message
));
93 http_message_init_type(message
, type
);
94 message
->parent
= NULL
;
95 phpstr_init(&message
->body
);
96 zend_hash_init(&message
->hdrs
, 0, NULL
, ZVAL_PTR_DTOR
, 0);
102 PHP_HTTP_API
void _http_message_set_type(http_message
*message
, http_message_type type
)
104 /* just act if different */
105 if (type
!= message
->type
) {
107 /* free request info */
108 switch (message
->type
)
110 case HTTP_MSG_REQUEST
:
111 STR_FREE(message
->http
.info
.request
.method
);
112 STR_FREE(message
->http
.info
.request
.URI
);
115 case HTTP_MSG_RESPONSE
:
116 STR_FREE(message
->http
.info
.response
.status
);
124 http_message_init_type(message
, type
);
128 PHP_HTTP_API http_message
*_http_message_parse_ex(http_message
*msg
, const char *message
, size_t message_length TSRMLS_DC
)
130 const char *body
= NULL
;
131 zend_bool free_msg
= msg
? 0 : 1;
133 if ((!message
) || (message_length
< HTTP_MSG_MIN_SIZE
)) {
137 msg
= http_message_init(msg
);
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
);
146 /* header parsing stops at (CR)LF (CR)LF */
147 if (body
= http_locate_body(message
)) {
149 const char *continue_at
= NULL
;
150 size_t remaining
= message
+ message_length
- body
;
152 /* message has chunked transfer encoding */
153 if ((c
= http_message_header(msg
, "Transfer-Encoding")) && (!strcasecmp("chunked", Z_STRVAL_P(c
)))) {
157 /* decode and replace Transfer-Encoding with Content-Length header */
158 if (continue_at
= http_encoding_dechunk(body
, message
+ message_length
- body
, &decoded
, &decoded_len
)) {
163 tmp_len
= (int) spprintf(&tmp
, 0, "%lu", (ulong
) decoded_len
);
165 ZVAL_STRINGL(len
, tmp
, tmp_len
, 0);
167 zend_hash_del(&msg
->hdrs
, "Transfer-Encoding", sizeof("Transfer-Encoding"));
168 zend_hash_del(&msg
->hdrs
, "Content-Length", sizeof("Content-Length"));
169 zend_hash_add(&msg
->hdrs
, "Content-Length", sizeof("Content-Length"), (void *) &len
, sizeof(zval
*), NULL
);
171 phpstr_from_string_ex(PHPSTR(msg
), decoded
, decoded_len
);
176 /* message has content-length header */
177 if (c
= http_message_header(msg
, "Content-Length")) {
178 ulong len
= strtoul(Z_STRVAL_P(c
), NULL
, 10);
179 if (len
> remaining
) {
180 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
);
183 phpstr_from_string_ex(PHPSTR(msg
), body
, len
);
184 continue_at
= body
+ len
;
187 /* message has content-range header */
188 if (c
= http_message_header(msg
, "Content-Range")) {
189 ulong total
= 0, start
= 0, end
= 0, len
= 0;
191 if (!strncasecmp(Z_STRVAL_P(c
), "bytes=", lenof("bytes="))) {
192 char *total_at
= NULL
, *end_at
= NULL
;
193 char *start_at
= Z_STRVAL_P(c
) + lenof("bytes=");
195 start
= strtoul(start_at
, &end_at
, 10);
197 end
= strtoul(end_at
+ 1, &total_at
, 10);
198 if (total_at
&& strncmp(total_at
+ 1, "*", 1)) {
199 total
= strtoul(total_at
+ 1, NULL
, 10);
201 if ((len
= (end
+ 1 - start
)) > remaining
) {
202 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
);
205 if (end
>= start
&& (!total
|| end
< total
)) {
206 phpstr_from_string_ex(PHPSTR(msg
), body
, len
);
207 continue_at
= body
+ len
;
213 http_error_ex(HE_WARNING
, HTTP_E_MALFORMED_HEADERS
, "Invalid Content-Range header: %s", Z_STRVAL_P(c
));
217 /* no headers that indicate content length */
218 if (HTTP_MSG_TYPE(RESPONSE
, msg
)) {
219 phpstr_from_string_ex(PHPSTR(msg
), body
, remaining
);
224 #if defined(HTTP_HAVE_ZLIB) || defined(HAVE_ZLIB)
225 /* check for compressed data */
226 if (c
= http_message_header(msg
, "Content-Encoding")) {
227 char *decoded
= NULL
;
228 size_t decoded_len
= 0;
229 # if defined(HAVE_ZLIB) && !defined(HTTP_HAVE_ZLIB)
230 zval func
, retval
, arg
, *args
[1];
234 ZVAL_STRINGL(&func
, "gzinflate", lenof("gzinflate"), 0);
236 # endif /* HAVE_ZLIB && !HTTP_HAVE_ZLIB */
238 # define DECODE_WITH_EXT_ZLIB() \
239 if (SUCCESS == call_user_function(EG(function_table), NULL, &func, &retval, 1, args TSRMLS_CC)) { \
240 if (Z_TYPE(retval) == IS_STRING) { \
241 decoded = Z_STRVAL(retval); \
242 decoded_len = Z_STRLEN(retval); \
246 if (!strcasecmp(Z_STRVAL_P(c
), "gzip") || !strcasecmp(Z_STRVAL_P(c
), "x-gzip")) {
247 # ifdef HTTP_HAVE_ZLIB
248 http_encoding_gzdecode(PHPSTR_VAL(msg
), PHPSTR_LEN(msg
), &decoded
, &decoded_len
);
250 ZVAL_STRINGL(&arg
, PHPSTR_VAL(msg
) + 10, PHPSTR_LEN(msg
) - 18, 0);
251 DECODE_WITH_EXT_ZLIB();
252 # endif /* HTTP_HAVE_ZLIB */
253 } else if (!strcasecmp(Z_STRVAL_P(c
), "deflate")) {
254 # ifdef HTTP_HAVE_ZLIB
255 http_encoding_inflate(PHPSTR_VAL(msg
), PHPSTR_LEN(msg
), &decoded
, &decoded_len
);
257 ZVAL_STRINGL(&arg
, PHPSTR_VAL(msg
), PHPSTR_LEN(msg
), 0);
258 DECODE_WITH_EXT_ZLIB();
259 # endif /* HTTP_HAVE_ZLIB */
262 if (decoded
&& decoded_len
) {
267 tmp_len
= (int) spprintf(&tmp
, 0, "%lu", (ulong
) decoded_len
);
269 ZVAL_STRINGL(len
, tmp
, tmp_len
, 0);
271 zend_hash_del(&msg
->hdrs
, "Content-Encoding", sizeof("Content-Encoding"));
272 zend_hash_del(&msg
->hdrs
, "Content-Length", sizeof("Content-Length"));
273 zend_hash_add(&msg
->hdrs
, "Content-Length", sizeof("Content-Length"), (void *) &len
, sizeof(zval
*), NULL
);
275 phpstr_dtor(PHPSTR(msg
));
276 PHPSTR(msg
)->data
= decoded
;
277 PHPSTR(msg
)->used
= decoded_len
;
278 PHPSTR(msg
)->free
= 1;
281 #endif /* HTTP_HAVE_ZLIB || HAVE_ZLIB */
283 /* check for following messages */
285 while (isspace(*continue_at
)) ++continue_at
;
286 if (continue_at
< (message
+ message_length
)) {
287 http_message
*next
= NULL
, *most
= NULL
;
289 /* set current message to parent of most parent following messages and return deepest */
290 if (most
= next
= http_message_parse(continue_at
, message
+ message_length
- continue_at
)) {
291 while (most
->parent
) most
= most
->parent
;
302 PHP_HTTP_API
void _http_message_tostring(http_message
*msg
, char **string
, size_t *length
)
309 phpstr_init_ex(&str
, 4096, 0);
313 case HTTP_MSG_REQUEST
:
314 phpstr_appendf(&str
, "%s %s HTTP/%1.1f" HTTP_CRLF
,
315 msg
->http
.info
.request
.method
,
316 msg
->http
.info
.request
.URI
,
320 case HTTP_MSG_RESPONSE
:
321 phpstr_appendf(&str
, "HTTP/%1.1f %d%s%s" HTTP_CRLF
,
323 msg
->http
.info
.response
.code
,
324 *msg
->http
.info
.response
.status
? " ":"",
325 msg
->http
.info
.response
.status
);
333 FOREACH_HASH_KEYVAL(&msg
->hdrs
, key
, idx
, header
) {
335 zval
**single_header
;
337 switch (Z_TYPE_PP(header
))
340 phpstr_appendf(&str
, "%s: %s" HTTP_CRLF
, key
, Z_STRVAL_PP(header
));
344 FOREACH_VAL(*header
, single_header
) {
345 phpstr_appendf(&str
, "%s: %s" HTTP_CRLF
, key
, Z_STRVAL_PP(single_header
));
354 if (PHPSTR_LEN(msg
)) {
355 phpstr_appends(&str
, HTTP_CRLF
);
356 phpstr_append(&str
, PHPSTR_VAL(msg
), PHPSTR_LEN(msg
));
357 phpstr_appends(&str
, HTTP_CRLF
);
360 data
= phpstr_data(&str
, string
, length
);
368 PHP_HTTP_API
void _http_message_serialize(http_message
*message
, char **string
, size_t *length
)
377 http_message_tostring(message
, &buf
, &len
);
378 phpstr_prepend(&str
, buf
, len
);
380 } while (message
= message
->parent
);
382 buf
= phpstr_data(&str
, string
, length
);
390 PHP_HTTP_API
void _http_message_tostruct_recursive(http_message
*msg
, zval
*obj TSRMLS_DC
)
395 INIT_ZARR(strct
, HASH_OF(obj
));
397 add_assoc_long(&strct
, "type", msg
->type
);
398 add_assoc_double(&strct
, "httpVersion", msg
->http
.version
);
401 case HTTP_MSG_RESPONSE
:
402 add_assoc_long(&strct
, "responseCode", msg
->http
.info
.response
.code
);
403 add_assoc_string(&strct
, "responseStatus", msg
->http
.info
.response
.status
, 1);
406 case HTTP_MSG_REQUEST
:
407 add_assoc_string(&strct
, "requestMethod", msg
->http
.info
.request
.method
, 1);
408 add_assoc_string(&strct
, "requestUri", msg
->http
.info
.request
.URI
, 1);
412 MAKE_STD_ZVAL(headers
);
414 zend_hash_copy(Z_ARRVAL_P(headers
), &msg
->hdrs
, (copy_ctor_func_t
) zval_add_ref
, NULL
, sizeof(zval
*));
415 add_assoc_zval(&strct
, "headers", headers
);
417 add_assoc_stringl(&strct
, "body", PHPSTR_VAL(msg
), PHPSTR_LEN(msg
), 1);
422 MAKE_STD_ZVAL(parent
);
423 if (Z_TYPE_P(obj
) == IS_ARRAY
) {
428 add_assoc_zval(&strct
, "parentMessage", parent
);
429 http_message_tostruct_recursive(msg
->parent
, parent
);
431 add_assoc_null(&strct
, "parentMessage");
435 PHP_HTTP_API STATUS
_http_message_send(http_message
*message TSRMLS_DC
)
439 switch (message
->type
)
441 case HTTP_MSG_RESPONSE
:
447 FOREACH_HASH_KEYVAL(&message
->hdrs
, key
, idx
, val
) {
449 if (Z_TYPE_PP(val
) == IS_ARRAY
) {
453 FOREACH_VAL(*val
, data
) {
454 http_send_header_ex(key
, strlen(key
), Z_STRVAL_PP(data
), Z_STRLEN_PP(data
), first
, NULL
);
458 http_send_header_ex(key
, strlen(key
), Z_STRVAL_PP(val
), Z_STRLEN_PP(val
), 1, NULL
);
463 rs
= SUCCESS
== http_send_status(message
->http
.info
.response
.code
) &&
464 SUCCESS
== http_send_data(PHPSTR_VAL(message
), PHPSTR_LEN(message
)) ?
469 case HTTP_MSG_REQUEST
:
471 #ifdef HTTP_HAVE_CURL
473 zval
**zhost
, options
, headers
;
475 INIT_PZVAL(&options
);
476 INIT_PZVAL(&headers
);
477 array_init(&options
);
478 array_init(&headers
);
479 zend_hash_copy(Z_ARRVAL(headers
), &message
->hdrs
, (copy_ctor_func_t
) zval_add_ref
, NULL
, sizeof(zval
*));
480 add_assoc_zval(&options
, "headers", &headers
);
482 /* check host header */
483 if (SUCCESS
== zend_hash_find(&message
->hdrs
, "Host", sizeof("Host"), (void **) &zhost
)) {
484 char *colon
= NULL
, *host
= NULL
;
489 if (colon
= strchr(Z_STRVAL_PP(zhost
), ':')) {
490 port
= atoi(colon
+ 1);
491 host
= estrndup(Z_STRVAL_PP(zhost
), host_len
= (Z_STRVAL_PP(zhost
) - colon
- 1));
493 host
= estrndup(Z_STRVAL_PP(zhost
), host_len
= Z_STRLEN_PP(zhost
));
495 uri
= http_absolute_uri_ex(
496 message
->http
.info
.request
.URI
, strlen(message
->http
.info
.request
.URI
),
497 NULL
, 0, host
, host_len
, port
);
500 uri
= http_absolute_uri(message
->http
.info
.request
.URI
);
503 if (!strcasecmp("POST", message
->http
.info
.request
.method
)) {
504 http_request_body body
= {HTTP_REQUEST_BODY_CSTRING
, PHPSTR_VAL(message
), PHPSTR_LEN(message
)};
505 rs
= http_post(uri
, &body
, Z_ARRVAL(options
), NULL
, NULL
);
507 if (!strcasecmp("GET", message
->http
.info
.request
.method
)) {
508 rs
= http_get(uri
, Z_ARRVAL(options
), NULL
, NULL
);
510 if (!strcasecmp("HEAD", message
->http
.info
.request
.method
)) {
511 rs
= http_head(uri
, Z_ARRVAL(options
), NULL
, NULL
);
513 http_error_ex(HE_WARNING
, HTTP_E_REQUEST_METHOD
,
514 "Cannot send HttpMessage. Request method %s not supported",
515 message
->http
.info
.request
.method
);
520 http_error(HE_WARNING
, HTTP_E_RUNTIME
, "HTTP requests not supported - ext/http was not linked against libcurl.");
527 http_error(HE_WARNING
, HTTP_E_MESSAGE_TYPE
, "HttpMessage is neither of type HTTP_MSG_REQUEST nor HTTP_MSG_RESPONSE");
534 PHP_HTTP_API http_message
*_http_message_dup(http_message
*msg TSRMLS_DC
)
540 char *serialized_data
;
541 size_t serialized_length
;
543 http_message_serialize(msg
, &serialized_data
, &serialized_length
);
544 new = http_message_parse(serialized_data
, serialized_length
);
545 efree(serialized_data
);
549 PHP_HTTP_API
void _http_message_dtor(http_message
*message
)
552 zend_hash_destroy(&message
->hdrs
);
553 phpstr_dtor(PHPSTR(message
));
555 switch (message
->type
)
557 case HTTP_MSG_REQUEST
:
558 STR_SET(message
->http
.info
.request
.method
, NULL
);
559 STR_SET(message
->http
.info
.request
.URI
, NULL
);
562 case HTTP_MSG_RESPONSE
:
563 STR_SET(message
->http
.info
.response
.status
, NULL
);
572 PHP_HTTP_API
void _http_message_free(http_message
**message
)
575 if ((*message
)->parent
) {
576 http_message_free(&(*message
)->parent
);
578 http_message_dtor(*message
);
589 * vim600: noet sw=4 ts=4 fdm=marker
590 * vim<600: noet sw=4 ts=4