- fixed issues ifndef HTTP_HAVE_ZLIB and enable gzipped response decoding ifdef HAVE_ZLIB
[m6w6/ext-http] / http_message_api.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 #ifdef HAVE_CONFIG_H
19 # include "config.h"
20 #endif
21 #include "php.h"
22
23 #include "php_http.h"
24 #include "php_http_std_defs.h"
25 #include "php_http_api.h"
26 #include "php_http_message_api.h"
27 #include "php_http_headers_api.h"
28 #include "php_http_send_api.h"
29 #include "php_http_request_api.h"
30 #include "php_http_url_api.h"
31 #include "php_http_encoding_api.h"
32
33 #include "phpstr/phpstr.h"
34
35 ZEND_EXTERN_MODULE_GLOBALS(http);
36
37 #define http_message_info_callback _http_message_info_callback
38 static void _http_message_info_callback(http_message **message, HashTable **headers, http_info *info TSRMLS_DC)
39 {
40 http_message *old = *message;
41
42 /* advance message */
43 if (old->type || zend_hash_num_elements(&old->hdrs) || PHPSTR_LEN(old)) {
44 (*message) = http_message_new();
45 (*message)->parent = old;
46 (*headers) = &((*message)->hdrs);
47 }
48
49 (*message)->http.version = info->http.version;
50
51 switch (info->type)
52 {
53 case IS_HTTP_REQUEST:
54 (*message)->type = HTTP_MSG_REQUEST;
55 HTTP_INFO(*message).request.URI = estrdup(HTTP_INFO(info).request.URI);
56 HTTP_INFO(*message).request.method = estrdup(HTTP_INFO(info).request.method);
57 break;
58
59 case IS_HTTP_RESPONSE:
60 (*message)->type = HTTP_MSG_RESPONSE;
61 HTTP_INFO(*message).response.code = HTTP_INFO(info).response.code;
62 HTTP_INFO(*message).response.status = estrdup(HTTP_INFO(info).response.status);
63 break;
64 }
65 }
66
67 #define http_message_init_type _http_message_init_type
68 static inline void _http_message_init_type(http_message *message, http_message_type type)
69 {
70 message->http.version = .0;
71
72 switch (message->type = type)
73 {
74 case HTTP_MSG_RESPONSE:
75 message->http.info.response.code = 0;
76 message->http.info.response.status = NULL;
77 break;
78
79 case HTTP_MSG_REQUEST:
80 message->http.info.request.method = NULL;
81 message->http.info.request.URI = NULL;
82 break;
83
84 case HTTP_MSG_NONE:
85 default:
86 break;
87 }
88 }
89
90 PHP_HTTP_API http_message *_http_message_init_ex(http_message *message, http_message_type type)
91 {
92 if (!message) {
93 message = ecalloc(1, sizeof(http_message));
94 }
95
96 http_message_init_type(message, type);
97 message->parent = NULL;
98 phpstr_init(&message->body);
99 zend_hash_init(&message->hdrs, 0, NULL, ZVAL_PTR_DTOR, 0);
100
101 return message;
102 }
103
104
105 PHP_HTTP_API void _http_message_set_type(http_message *message, http_message_type type)
106 {
107 /* just act if different */
108 if (type != message->type) {
109
110 /* free request info */
111 switch (message->type)
112 {
113 case HTTP_MSG_REQUEST:
114 STR_FREE(message->http.info.request.method);
115 STR_FREE(message->http.info.request.URI);
116 break;
117
118 case HTTP_MSG_RESPONSE:
119 STR_FREE(message->http.info.response.status);
120 break;
121
122 default:
123 break;
124 }
125
126 /* init */
127 http_message_init_type(message, type);
128 }
129 }
130
131 PHP_HTTP_API http_message *_http_message_parse_ex(http_message *msg, const char *message, size_t message_length TSRMLS_DC)
132 {
133 const char *body = NULL;
134 zend_bool free_msg = msg ? 0 : 1;
135
136 if ((!message) || (message_length < HTTP_MSG_MIN_SIZE)) {
137 return NULL;
138 }
139
140 msg = http_message_init(msg);
141
142 if (SUCCESS != http_parse_headers_cb(message, &msg->hdrs, 1, (http_info_callback) http_message_info_callback, (void **) &msg)) {
143 if (free_msg) {
144 http_message_free(&msg);
145 }
146 return NULL;
147 }
148
149 /* header parsing stops at (CR)LF (CR)LF */
150 if (body = http_locate_body(message)) {
151 zval *c;
152 const char *continue_at = NULL;
153
154 /* message has chunked transfer encoding */
155 if ((c = http_message_header(msg, "Transfer-Encoding")) && (!strcasecmp("chunked", Z_STRVAL_P(c)))) {
156 char *decoded;
157 size_t decoded_len;
158
159 /* decode and replace Transfer-Encoding with Content-Length header */
160 if (continue_at = http_encoding_dechunk(body, message + message_length - body, &decoded, &decoded_len)) {
161 zval *len;
162 char *tmp;
163 int tmp_len;
164
165 tmp_len = (int) spprintf(&tmp, 0, "%lu", (ulong) decoded_len);
166 MAKE_STD_ZVAL(len);
167 ZVAL_STRINGL(len, tmp, tmp_len, 0);
168
169 zend_hash_del(&msg->hdrs, "Transfer-Encoding", sizeof("Transfer-Encoding"));
170 zend_hash_del(&msg->hdrs, "Content-Length", sizeof("Content-Length"));
171 zend_hash_add(&msg->hdrs, "Content-Length", sizeof("Content-Length"), (void *) &len, sizeof(zval *), NULL);
172
173 phpstr_from_string_ex(PHPSTR(msg), decoded, decoded_len);
174 efree(decoded);
175 }
176 } else
177
178 /* message has content-length header */
179 if (c = http_message_header(msg, "Content-Length")) {
180 long len = atol(Z_STRVAL_P(c));
181 phpstr_from_string_ex(PHPSTR(msg), body, len);
182 continue_at = body + len;
183 } else
184
185 /* message has content-range header */
186 if (c = http_message_header(msg, "Content-Range")) {
187 ulong total = 0, start = 0, end = 0;
188
189 if (!strncasecmp(Z_STRVAL_P(c), "bytes=", lenof("bytes="))) {
190 char *total_at = NULL, *end_at = NULL;
191 char *start_at = Z_STRVAL_P(c) + lenof("bytes=");
192
193 start = strtoul(start_at, &end_at, 10);
194 if (end_at) {
195 end = strtoul(end_at + 1, &total_at, 10);
196 if (total_at && strncmp(total_at + 1, "*", 1)) {
197 total = strtoul(total_at + 1, NULL, 10);
198 }
199
200 if (end >= start && (!total || end < total)) {
201 phpstr_from_string_ex(PHPSTR(msg), body, (size_t) (end + 1 - start));
202 continue_at = body + (end + 1 - start);
203 }
204 }
205 }
206
207 if (!continue_at) {
208 http_error_ex(HE_WARNING, HTTP_E_MALFORMED_HEADERS, "Invalid Content-Range header: %s", Z_STRVAL_P(c));
209 }
210 } else
211
212 /* no headers that indicate content length */
213 if (HTTP_MSG_TYPE(RESPONSE, msg)) {
214 phpstr_from_string_ex(PHPSTR(msg), body, message + message_length - body);
215 } else {
216 continue_at = body;
217 }
218
219 #if defined(HTTP_HAVE_ZLIB) || defined(HAVE_ZLIB)
220 /* check for compressed data */
221 if (c = http_message_header(msg, "Content-Encoding")) {
222 char *decoded = NULL;
223 size_t decoded_len = 0;
224 # ifdef HAVE_ZLIB
225 zval func, retval, arg, *args[1];
226 INIT_PZVAL(&func);
227 INIT_PZVAL(&retval);
228 INIT_PZVAL(&arg);
229 ZVAL_STRINGL(&func, "gzinflate", lenof("gzinflate"), 0);
230 args[0] = &arg;
231 # endif /* HAVE_ZLIB */
232
233 # define DECODE_WITH_EXT_ZLIB() \
234 if (SUCCESS == call_user_function(EG(function_table), NULL, &func, &retval, 1, args TSRMLS_CC)) { \
235 if (Z_TYPE(retval) == IS_STRING) { \
236 decoded = Z_STRVAL(retval); \
237 decoded_len = Z_STRLEN(retval); \
238 } \
239 }
240
241 if (!strcasecmp(Z_STRVAL_P(c), "gzip") || !strcasecmp(Z_STRVAL_P(c), "x-gzip")) {
242 # ifdef HTTP_HAVE_ZLIB
243 http_encoding_gzdecode(PHPSTR_VAL(msg), PHPSTR_LEN(msg), &decoded, &decoded_len);
244 # else
245 ZVAL_STRINGL(&arg, PHPSTR_VAL(msg) + 10, PHPSTR_LEN(msg) - 18, 0);
246 DECODE_WITH_EXT_ZLIB();
247 # endif /* HTTP_HAVE_ZLIB */
248 } else if (!strcasecmp(Z_STRVAL_P(c), "deflate")) {
249 # ifdef HTTP_HAVE_ZLIB
250 http_encoding_inflate(PHPSTR_VAL(msg), PHPSTR_LEN(msg), &decoded, &decoded_len);
251 # else
252 ZVAL_STRINGL(&arg, PHPSTR_VAL(msg), PHPSTR_LEN(msg), 0);
253 DECODE_WITH_EXT_ZLIB();
254 # endif /* HTTP_HAVE_ZLIB */
255 }
256
257 if (decoded && decoded_len) {
258 zval *len;
259 char *tmp;
260 int tmp_len;
261
262 tmp_len = (int) spprintf(&tmp, 0, "%lu", (ulong) decoded_len);
263 MAKE_STD_ZVAL(len);
264 ZVAL_STRINGL(len, tmp, tmp_len, 0);
265
266 zend_hash_del(&msg->hdrs, "Content-Encoding", sizeof("Content-Encoding"));
267 zend_hash_del(&msg->hdrs, "Content-Length", sizeof("Content-Length"));
268 zend_hash_add(&msg->hdrs, "Content-Length", sizeof("Content-Length"), (void *) &len, sizeof(zval *), NULL);
269
270 phpstr_dtor(PHPSTR(msg));
271 PHPSTR(msg)->data = decoded;
272 PHPSTR(msg)->used = decoded_len;
273 PHPSTR(msg)->free = 1;
274 }
275 }
276 #endif /* HTTP_HAVE_ZLIB || HAVE_ZLIB */
277
278 /* check for following messages */
279 if (continue_at) {
280 while (isspace(*continue_at)) ++continue_at;
281 if (continue_at < (message + message_length)) {
282 http_message *next = NULL, *most = NULL;
283
284 /* set current message to parent of most parent following messages and return deepest */
285 if (most = next = http_message_parse(continue_at, message + message_length - continue_at)) {
286 while (most->parent) most = most->parent;
287 most->parent = msg;
288 msg = next;
289 }
290 }
291 }
292 }
293
294 return msg;
295 }
296
297 PHP_HTTP_API void _http_message_tostring(http_message *msg, char **string, size_t *length)
298 {
299 phpstr str;
300 char *key, *data;
301 ulong idx;
302 zval **header;
303
304 phpstr_init_ex(&str, 4096, 0);
305
306 switch (msg->type)
307 {
308 case HTTP_MSG_REQUEST:
309 phpstr_appendf(&str, "%s %s HTTP/%1.1f" HTTP_CRLF,
310 msg->http.info.request.method,
311 msg->http.info.request.URI,
312 msg->http.version);
313 break;
314
315 case HTTP_MSG_RESPONSE:
316 phpstr_appendf(&str, "HTTP/%1.1f %d%s%s" HTTP_CRLF,
317 msg->http.version,
318 msg->http.info.response.code,
319 *msg->http.info.response.status ? " ":"",
320 msg->http.info.response.status);
321 break;
322
323 case HTTP_MSG_NONE:
324 default:
325 break;
326 }
327
328 FOREACH_HASH_KEYVAL(&msg->hdrs, key, idx, header) {
329 if (key) {
330 zval **single_header;
331
332 switch (Z_TYPE_PP(header))
333 {
334 case IS_STRING:
335 phpstr_appendf(&str, "%s: %s" HTTP_CRLF, key, Z_STRVAL_PP(header));
336 break;
337
338 case IS_ARRAY:
339 FOREACH_VAL(*header, single_header) {
340 phpstr_appendf(&str, "%s: %s" HTTP_CRLF, key, Z_STRVAL_PP(single_header));
341 }
342 break;
343 }
344
345 key = NULL;
346 }
347 }
348
349 if (PHPSTR_LEN(msg)) {
350 phpstr_appends(&str, HTTP_CRLF);
351 phpstr_append(&str, PHPSTR_VAL(msg), PHPSTR_LEN(msg));
352 phpstr_appends(&str, HTTP_CRLF);
353 }
354
355 data = phpstr_data(&str, string, length);
356 if (!string) {
357 efree(data);
358 }
359
360 phpstr_dtor(&str);
361 }
362
363 PHP_HTTP_API void _http_message_serialize(http_message *message, char **string, size_t *length)
364 {
365 char *buf;
366 size_t len;
367 phpstr str;
368
369 phpstr_init(&str);
370
371 do {
372 http_message_tostring(message, &buf, &len);
373 phpstr_prepend(&str, buf, len);
374 efree(buf);
375 } while (message = message->parent);
376
377 buf = phpstr_data(&str, string, length);
378 if (!string) {
379 efree(buf);
380 }
381
382 phpstr_dtor(&str);
383 }
384
385 PHP_HTTP_API void _http_message_tostruct_recursive(http_message *msg, zval *obj TSRMLS_DC)
386 {
387 zval strct;
388 zval *headers;
389
390 INIT_ZARR(strct, HASH_OF(obj));
391
392 add_assoc_long(&strct, "type", msg->type);
393 add_assoc_double(&strct, "httpVersion", msg->http.version);
394 switch (msg->type)
395 {
396 case HTTP_MSG_RESPONSE:
397 add_assoc_long(&strct, "responseCode", msg->http.info.response.code);
398 add_assoc_string(&strct, "responseStatus", msg->http.info.response.status, 1);
399 break;
400
401 case HTTP_MSG_REQUEST:
402 add_assoc_string(&strct, "requestMethod", msg->http.info.request.method, 1);
403 add_assoc_string(&strct, "requestUri", msg->http.info.request.URI, 1);
404 break;
405 }
406
407 MAKE_STD_ZVAL(headers);
408 array_init(headers);
409 zend_hash_copy(Z_ARRVAL_P(headers), &msg->hdrs, (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval *));
410 add_assoc_zval(&strct, "headers", headers);
411
412 add_assoc_stringl(&strct, "body", PHPSTR_VAL(msg), PHPSTR_LEN(msg), 1);
413
414 if (msg->parent) {
415 zval *parent;
416
417 MAKE_STD_ZVAL(parent);
418 if (Z_TYPE_P(obj) == IS_ARRAY) {
419 array_init(parent);
420 } else {
421 object_init(parent);
422 }
423 add_assoc_zval(&strct, "parentMessage", parent);
424 http_message_tostruct_recursive(msg->parent, parent);
425 } else {
426 add_assoc_null(&strct, "parentMessage");
427 }
428 }
429
430 PHP_HTTP_API STATUS _http_message_send(http_message *message TSRMLS_DC)
431 {
432 STATUS rs = FAILURE;
433
434 switch (message->type)
435 {
436 case HTTP_MSG_RESPONSE:
437 {
438 char *key;
439 ulong idx;
440 zval **val;
441
442 FOREACH_HASH_KEYVAL(&message->hdrs, key, idx, val) {
443 if (key) {
444 if (Z_TYPE_PP(val) == IS_ARRAY) {
445 zend_bool first = 1;
446 zval **data;
447
448 FOREACH_VAL(*val, data) {
449 http_send_header_ex(key, strlen(key), Z_STRVAL_PP(data), Z_STRLEN_PP(data), first, NULL);
450 first = 0;
451 }
452 } else {
453 http_send_header_ex(key, strlen(key), Z_STRVAL_PP(val), Z_STRLEN_PP(val), 1, NULL);
454 }
455 key = NULL;
456 }
457 }
458 rs = SUCCESS == http_send_status(message->http.info.response.code) &&
459 SUCCESS == http_send_data(PHPSTR_VAL(message), PHPSTR_LEN(message)) ?
460 SUCCESS : FAILURE;
461 }
462 break;
463
464 case HTTP_MSG_REQUEST:
465 {
466 #ifdef HTTP_HAVE_CURL
467 char *uri = NULL;
468 zval **zhost, options, headers;
469
470 INIT_PZVAL(&options);
471 INIT_PZVAL(&headers);
472 array_init(&options);
473 array_init(&headers);
474 zend_hash_copy(Z_ARRVAL(headers), &message->hdrs, (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval *));
475 add_assoc_zval(&options, "headers", &headers);
476
477 /* check host header */
478 if (SUCCESS == zend_hash_find(&message->hdrs, "Host", sizeof("Host"), (void **) &zhost)) {
479 char *colon = NULL, *host = NULL;
480 size_t host_len = 0;
481 int port = 0;
482
483 /* check for port */
484 if (colon = strchr(Z_STRVAL_PP(zhost), ':')) {
485 port = atoi(colon + 1);
486 host = estrndup(Z_STRVAL_PP(zhost), host_len = (Z_STRVAL_PP(zhost) - colon - 1));
487 } else {
488 host = estrndup(Z_STRVAL_PP(zhost), host_len = Z_STRLEN_PP(zhost));
489 }
490 uri = http_absolute_uri_ex(
491 message->http.info.request.URI, strlen(message->http.info.request.URI),
492 NULL, 0, host, host_len, port);
493 efree(host);
494 } else {
495 uri = http_absolute_uri(message->http.info.request.URI);
496 }
497
498 if (!strcasecmp("POST", message->http.info.request.method)) {
499 http_request_body body = {HTTP_REQUEST_BODY_CSTRING, PHPSTR_VAL(message), PHPSTR_LEN(message)};
500 rs = http_post(uri, &body, Z_ARRVAL(options), NULL, NULL);
501 } else
502 if (!strcasecmp("GET", message->http.info.request.method)) {
503 rs = http_get(uri, Z_ARRVAL(options), NULL, NULL);
504 } else
505 if (!strcasecmp("HEAD", message->http.info.request.method)) {
506 rs = http_head(uri, Z_ARRVAL(options), NULL, NULL);
507 } else {
508 http_error_ex(HE_WARNING, HTTP_E_REQUEST_METHOD,
509 "Cannot send HttpMessage. Request method %s not supported",
510 message->http.info.request.method);
511 }
512
513 efree(uri);
514 #else
515 http_error(HE_WARNING, HTTP_E_RUNTIME, "HTTP requests not supported - ext/http was not linked against libcurl.");
516 #endif
517 }
518 break;
519
520 case HTTP_MSG_NONE:
521 default:
522 http_error(HE_WARNING, HTTP_E_MESSAGE_TYPE, "HttpMessage is neither of type HTTP_MSG_REQUEST nor HTTP_MSG_RESPONSE");
523 break;
524 }
525
526 return rs;
527 }
528
529 PHP_HTTP_API http_message *_http_message_dup(http_message *msg TSRMLS_DC)
530 {
531 /*
532 * TODO: unroll
533 */
534 http_message *new;
535 char *serialized_data;
536 size_t serialized_length;
537
538 http_message_serialize(msg, &serialized_data, &serialized_length);
539 new = http_message_parse(serialized_data, serialized_length);
540 efree(serialized_data);
541 return new;
542 }
543
544 PHP_HTTP_API void _http_message_dtor(http_message *message)
545 {
546 if (message) {
547 zend_hash_destroy(&message->hdrs);
548 phpstr_dtor(PHPSTR(message));
549
550 switch (message->type)
551 {
552 case HTTP_MSG_REQUEST:
553 STR_SET(message->http.info.request.method, NULL);
554 STR_SET(message->http.info.request.URI, NULL);
555 break;
556
557 case HTTP_MSG_RESPONSE:
558 STR_SET(message->http.info.response.status, NULL);
559 break;
560
561 default:
562 break;
563 }
564 }
565 }
566
567 PHP_HTTP_API void _http_message_free(http_message **message)
568 {
569 if (*message) {
570 if ((*message)->parent) {
571 http_message_free(&(*message)->parent);
572 }
573 http_message_dtor(*message);
574 efree(*message);
575 *message = NULL;
576 }
577 }
578
579 /*
580 * Local variables:
581 * tab-width: 4
582 * c-basic-offset: 4
583 * End:
584 * vim600: noet sw=4 ts=4 fdm=marker
585 * vim<600: noet sw=4 ts=4
586 */
587