- release 1.4.1
[m6w6/ext-http] / http_message_api.c
1 /*
2 +--------------------------------------------------------------------+
3 | PECL :: http |
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 +--------------------------------------------------------------------+
11 */
12
13 /* $Id$ */
14
15 #define HTTP_WANT_SAPI
16 #define HTTP_WANT_CURL
17 #define HTTP_WANT_ZLIB
18 #include "php_http.h"
19
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"
27
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)
30 {
31 http_message *old = *message;
32
33 /* advance 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);
38 }
39
40 http_message_set_info(*message, info);
41 }
42
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)
45 {
46 message->http.version = .0;
47
48 switch (message->type = type) {
49 case HTTP_MSG_RESPONSE:
50 message->http.info.response.code = 0;
51 message->http.info.response.status = NULL;
52 break;
53
54 case HTTP_MSG_REQUEST:
55 message->http.info.request.method = NULL;
56 message->http.info.request.url = NULL;
57 break;
58
59 case HTTP_MSG_NONE:
60 default:
61 break;
62 }
63 }
64
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)
66 {
67 if (!message) {
68 message = ecalloc_rel(1, sizeof(http_message));
69 }
70
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);
75
76 return message;
77 }
78
79
80 PHP_HTTP_API void _http_message_set_type(http_message *message, http_message_type type)
81 {
82 /* just act if different */
83 if (type != message->type) {
84
85 /* free request info */
86 switch (message->type) {
87 case HTTP_MSG_REQUEST:
88 STR_FREE(message->http.info.request.method);
89 STR_FREE(message->http.info.request.url);
90 break;
91
92 case HTTP_MSG_RESPONSE:
93 STR_FREE(message->http.info.response.status);
94 break;
95
96 default:
97 break;
98 }
99
100 /* init */
101 http_message_init_type(message, type);
102 }
103 }
104
105 PHP_HTTP_API void _http_message_set_info(http_message *message, http_info *info)
106 {
107 http_message_set_type(message, info->type);
108 message->http.version = info->http.version;
109 switch (message->type) {
110 case IS_HTTP_REQUEST:
111 STR_SET(HTTP_INFO(message).request.url, HTTP_INFO(info).request.url ? estrdup(HTTP_INFO(info).request.url) : NULL);
112 STR_SET(HTTP_INFO(message).request.method, HTTP_INFO(info).request.method ? estrdup(HTTP_INFO(info).request.method) : NULL);
113 break;
114
115 case IS_HTTP_RESPONSE:
116 HTTP_INFO(message).response.code = HTTP_INFO(info).response.code;
117 STR_SET(HTTP_INFO(message).response.status, HTTP_INFO(info).response.status ? estrdup(HTTP_INFO(info).response.status) : NULL);
118 break;
119
120 default:
121 break;
122 }
123 }
124
125 #define http_message_body_parse(m, ms, ml, c) _http_message_body_parse((m), (ms), (ml), (c) TSRMLS_CC)
126 static inline void _http_message_body_parse(http_message *msg, const char *message, size_t message_length, const char **continue_at TSRMLS_DC)
127 {
128 zval *c;
129 size_t remaining;
130 const char *body;
131
132 *continue_at = NULL;
133 if ((body = http_locate_body(message))) {
134 remaining = message + message_length - body;
135
136 if ((c = http_message_header(msg, "Transfer-Encoding"))) {
137 if (strstr(Z_STRVAL_P(c), "chunked")) {
138 /* message has chunked transfer encoding */
139 char *decoded;
140 size_t decoded_len;
141
142 /* decode and replace Transfer-Encoding with Content-Length header */
143 if ((*continue_at = http_encoding_dechunk(body, message + message_length - body, &decoded, &decoded_len))) {
144 zval *len;
145 char *tmp;
146 int tmp_len;
147
148 tmp_len = (int) spprintf(&tmp, 0, "%zu", decoded_len);
149 MAKE_STD_ZVAL(len);
150 ZVAL_STRINGL(len, tmp, tmp_len, 0);
151
152 ZVAL_ADDREF(c);
153 zend_hash_update(&msg->hdrs, "X-Original-Transfer-Encoding", sizeof("X-Original-Transfer-Encoding"), (void *) &c, sizeof(zval *), NULL);
154 zend_hash_del(&msg->hdrs, "Transfer-Encoding", sizeof("Transfer-Encoding"));
155 zend_hash_del(&msg->hdrs, "Content-Length", sizeof("Content-Length"));
156 zend_hash_update(&msg->hdrs, "Content-Length", sizeof("Content-Length"), (void *) &len, sizeof(zval *), NULL);
157
158 phpstr_from_string_ex(PHPSTR(msg), decoded, decoded_len);
159 efree(decoded);
160 }
161 }
162 zval_ptr_dtor(&c);
163 }
164
165 if (!*continue_at && (c = http_message_header(msg, "Content-Length"))) {
166 /* message has content-length header */
167 ulong len = strtoul(Z_STRVAL_P(c), NULL, 10);
168 if (len > remaining) {
169 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);
170 len = remaining;
171 }
172 phpstr_from_string_ex(PHPSTR(msg), body, len);
173 *continue_at = body + len;
174 zval_ptr_dtor(&c);
175 }
176
177 if (!*continue_at && (c = http_message_header(msg, "Content-Range"))) {
178 /* message has content-range header */
179 ulong total = 0, start = 0, end = 0, len = 0;
180
181 if (!strncasecmp(Z_STRVAL_P(c), "bytes", lenof("bytes")) &&
182 ( Z_STRVAL_P(c)[lenof("bytes")] == ':' ||
183 Z_STRVAL_P(c)[lenof("bytes")] == ' ' ||
184 Z_STRVAL_P(c)[lenof("bytes")] == '=')) {
185 char *total_at = NULL, *end_at = NULL;
186 char *start_at = Z_STRVAL_P(c) + sizeof("bytes");
187
188 start = strtoul(start_at, &end_at, 10);
189 if (end_at) {
190 end = strtoul(end_at + 1, &total_at, 10);
191 if (total_at && strncmp(total_at + 1, "*", 1)) {
192 total = strtoul(total_at + 1, NULL, 10);
193 }
194 if ((len = (end + 1 - start)) > remaining) {
195 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);
196 len = remaining;
197 }
198 if (end >= start && (!total || end < total)) {
199 phpstr_from_string_ex(PHPSTR(msg), body, len);
200 *continue_at = body + len;
201 }
202 }
203 }
204
205 if (!*continue_at) {
206 http_error_ex(HE_WARNING, HTTP_E_MALFORMED_HEADERS, "Invalid Content-Range header: %s", Z_STRVAL_P(c));
207 }
208 zval_ptr_dtor(&c);
209 }
210
211 if (!*continue_at) {
212 /* no headers that indicate content length */
213 if (HTTP_MSG_TYPE(RESPONSE, msg)) {
214 phpstr_from_string_ex(PHPSTR(msg), body, remaining);
215 } else {
216 *continue_at = body;
217 }
218 }
219
220 #ifdef HTTP_HAVE_ZLIB
221 /* check for compressed data */
222 if ((c = http_message_header(msg, "Vary"))) {
223 zval_ptr_dtor(&c);
224
225 if ((c = http_message_header(msg, "Content-Encoding"))) {
226 char *decoded = NULL;
227 size_t decoded_len = 0;
228
229 if ( !strcasecmp(Z_STRVAL_P(c), "gzip") ||
230 !strcasecmp(Z_STRVAL_P(c), "x-gzip") ||
231 !strcasecmp(Z_STRVAL_P(c), "deflate")) {
232 http_encoding_inflate(PHPSTR_VAL(msg), PHPSTR_LEN(msg), &decoded, &decoded_len);
233 }
234
235 if (decoded) {
236 zval *len, **original_len;
237 char *tmp;
238 int tmp_len;
239
240 tmp_len = (int) spprintf(&tmp, 0, "%zu", decoded_len);
241 MAKE_STD_ZVAL(len);
242 ZVAL_STRINGL(len, tmp, tmp_len, 0);
243
244 ZVAL_ADDREF(c);
245 zend_hash_update(&msg->hdrs, "X-Original-Content-Encoding", sizeof("X-Original-Content-Encoding"), (void *) &c, sizeof(zval *), NULL);
246 zend_hash_del(&msg->hdrs, "Content-Encoding", sizeof("Content-Encoding"));
247 if (SUCCESS == zend_hash_find(&msg->hdrs, "Content-Length", sizeof("Content-Length"), (void *) &original_len)) {
248 ZVAL_ADDREF(*original_len);
249 zend_hash_update(&msg->hdrs, "X-Original-Content-Length", sizeof("X-Original-Content-Length"), (void *) original_len, sizeof(zval *), NULL);
250 zend_hash_update(&msg->hdrs, "Content-Length", sizeof("Content-Length"), (void *) &len, sizeof(zval *), NULL);
251 } else {
252 zend_hash_update(&msg->hdrs, "Content-Length", sizeof("Content-Length"), (void *) &len, sizeof(zval *), NULL);
253 }
254
255 phpstr_dtor(PHPSTR(msg));
256 PHPSTR(msg)->data = decoded;
257 PHPSTR(msg)->used = decoded_len;
258 PHPSTR(msg)->free = 1;
259 }
260
261 zval_ptr_dtor(&c);
262 }
263 }
264 #endif /* HTTP_HAVE_ZLIB */
265 }
266 }
267
268 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)
269 {
270 const char *continue_at;
271 zend_bool free_msg = msg ? 0 : 1;
272
273 if ((!message) || (message_length < HTTP_MSG_MIN_SIZE)) {
274 http_error_ex(HE_WARNING, HTTP_E_INVALID_PARAM, "Empty or too short HTTP message: '%s'", message);
275 return NULL;
276 }
277
278 msg = http_message_init_rel(msg, 0);
279
280 if (SUCCESS != http_parse_headers_cb(message, &msg->hdrs, 1, (http_info_callback) http_message_info_callback, (void *) &msg)) {
281 if (free_msg) {
282 http_message_free(&msg);
283 }
284 http_error(HE_WARNING, HTTP_E_MALFORMED_HEADERS, "Failed to parse message headers");
285 return NULL;
286 }
287
288 http_message_body_parse(msg, message, message_length, &continue_at);
289
290 /* check for following messages */
291 if (continue_at && (continue_at < (message + message_length))) {
292 while (HTTP_IS_CTYPE(space, *continue_at)) ++continue_at;
293 if (continue_at < (message + message_length)) {
294 http_message *next = NULL, *most = NULL;
295
296 /* set current message to parent of most parent following messages and return deepest */
297 if ((most = next = http_message_parse_rel(NULL, continue_at, message + message_length - continue_at))) {
298 while (most->parent) most = most->parent;
299 most->parent = msg;
300 msg = next;
301 }
302 }
303 }
304
305 return msg;
306 }
307
308 PHP_HTTP_API void _http_message_tostring(http_message *msg, char **string, size_t *length)
309 {
310 phpstr str;
311 HashKey key = initHashKey(0);
312 zval **header;
313 char *data;
314 HashPosition pos1;
315
316 phpstr_init_ex(&str, 4096, 0);
317
318 switch (msg->type) {
319 case HTTP_MSG_REQUEST:
320 phpstr_appendf(&str, HTTP_INFO_REQUEST_FMT_ARGS(&msg->http, HTTP_CRLF));
321 break;
322
323 case HTTP_MSG_RESPONSE:
324 phpstr_appendf(&str, HTTP_INFO_RESPONSE_FMT_ARGS(&msg->http, HTTP_CRLF));
325 break;
326
327 case HTTP_MSG_NONE:
328 default:
329 break;
330 }
331
332 FOREACH_HASH_KEYVAL(pos1, &msg->hdrs, key, header) {
333 if (key.type == HASH_KEY_IS_STRING) {
334 HashPosition pos2;
335 zval **single_header;
336
337 switch (Z_TYPE_PP(header)) {
338 case IS_STRING:
339 phpstr_appendf(&str, "%s: %s" HTTP_CRLF, key.str, Z_STRVAL_PP(header));
340 break;
341
342 case IS_ARRAY:
343 FOREACH_VAL(pos2, *header, single_header) {
344 phpstr_appendf(&str, "%s: %s" HTTP_CRLF, key.str, Z_STRVAL_PP(single_header));
345 }
346 break;
347 }
348 }
349 }
350
351 if (PHPSTR_LEN(msg)) {
352 phpstr_appends(&str, HTTP_CRLF);
353 phpstr_append(&str, PHPSTR_VAL(msg), PHPSTR_LEN(msg));
354 phpstr_appends(&str, HTTP_CRLF);
355 }
356
357 data = phpstr_data(&str, string, length);
358 if (!string) {
359 efree(data);
360 }
361
362 phpstr_dtor(&str);
363 }
364
365 PHP_HTTP_API void _http_message_serialize(http_message *message, char **string, size_t *length)
366 {
367 char *buf;
368 size_t len;
369 phpstr str;
370
371 phpstr_init(&str);
372
373 do {
374 http_message_tostring(message, &buf, &len);
375 phpstr_prepend(&str, buf, len);
376 efree(buf);
377 } while ((message = message->parent));
378
379 buf = phpstr_data(&str, string, length);
380 if (!string) {
381 efree(buf);
382 }
383
384 phpstr_dtor(&str);
385 }
386
387 PHP_HTTP_API http_message *_http_message_reverse(http_message *msg)
388 {
389 int i, c;
390
391 http_message_count(c, msg);
392
393 if (c > 1) {
394 http_message *tmp = msg, **arr = ecalloc(c, sizeof(http_message *));
395
396 for (i = 0; i < c; ++i) {
397 arr[i] = tmp;
398 tmp = tmp->parent;
399 }
400 arr[0]->parent = NULL;
401 for (i = 0; i < c-1; ++i) {
402 arr[i+1]->parent = arr[i];
403 }
404
405 msg = arr[c-1];
406 efree(arr);
407 }
408
409 return msg;
410 }
411
412 PHP_HTTP_API http_message *_http_message_interconnect(http_message *m1, http_message *m2)
413 {
414 if (m1 && m2) {
415 int i = 0, c1, c2;
416 http_message *t1 = m1, *t2 = m2, *p1, *p2;
417
418 http_message_count(c1, m1);
419 http_message_count(c2, m2);
420
421 while (i++ < (c1 - c2)) {
422 t1 = t1->parent;
423 }
424 while (i++ <= c1) {
425 p1 = t1->parent;
426 p2 = t2->parent;
427 t1->parent = t2;
428 t2->parent = p1;
429 t1 = p1;
430 t2 = p2;
431 }
432 } else if (!m1 && m2) {
433 m1 = m2;
434 }
435 return m1;
436 }
437
438 PHP_HTTP_API void _http_message_tostruct_recursive(http_message *msg, zval *obj TSRMLS_DC)
439 {
440 zval strct;
441 zval *headers;
442
443 INIT_ZARR(strct, HASH_OF(obj));
444
445 add_assoc_long(&strct, "type", msg->type);
446 add_assoc_double(&strct, "httpVersion", msg->http.version);
447 switch (msg->type)
448 {
449 case HTTP_MSG_RESPONSE:
450 add_assoc_long(&strct, "responseCode", msg->http.info.response.code);
451 add_assoc_string(&strct, "responseStatus", STR_PTR(msg->http.info.response.status), 1);
452 break;
453
454 case HTTP_MSG_REQUEST:
455 add_assoc_string(&strct, "requestMethod", STR_PTR(msg->http.info.request.method), 1);
456 add_assoc_string(&strct, "requestUrl", STR_PTR(msg->http.info.request.url), 1);
457 break;
458
459 case HTTP_MSG_NONE:
460 /* avoid compiler warning */
461 break;
462 }
463
464 MAKE_STD_ZVAL(headers);
465 array_init(headers);
466 zend_hash_copy(Z_ARRVAL_P(headers), &msg->hdrs, (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval *));
467 add_assoc_zval(&strct, "headers", headers);
468
469 add_assoc_stringl(&strct, "body", PHPSTR_VAL(msg), PHPSTR_LEN(msg), 1);
470
471 if (msg->parent) {
472 zval *parent;
473
474 MAKE_STD_ZVAL(parent);
475 if (Z_TYPE_P(obj) == IS_ARRAY) {
476 array_init(parent);
477 } else {
478 object_init(parent);
479 }
480 add_assoc_zval(&strct, "parentMessage", parent);
481 http_message_tostruct_recursive(msg->parent, parent);
482 } else {
483 add_assoc_null(&strct, "parentMessage");
484 }
485 }
486
487 PHP_HTTP_API STATUS _http_message_send(http_message *message TSRMLS_DC)
488 {
489 STATUS rs = FAILURE;
490
491 switch (message->type) {
492 case HTTP_MSG_RESPONSE:
493 {
494 HashKey key = initHashKey(0);
495 zval **val;
496 HashPosition pos;
497
498 FOREACH_HASH_KEYVAL(pos, &message->hdrs, key, val) {
499 if (key.type == HASH_KEY_IS_STRING) {
500 http_send_header_zval_ex(key.str, key.len-1, val, 1);
501 }
502 }
503 rs = SUCCESS == http_send_status(message->http.info.response.code) &&
504 SUCCESS == http_send_data(PHPSTR_VAL(message), PHPSTR_LEN(message)) ?
505 SUCCESS : FAILURE;
506 break;
507 }
508
509 case HTTP_MSG_REQUEST:
510 {
511 #ifdef HTTP_HAVE_CURL
512 char *uri = NULL;
513 http_request request;
514 zval **zhost, options, headers;
515
516 INIT_PZVAL(&options);
517 INIT_PZVAL(&headers);
518 array_init(&options);
519 array_init(&headers);
520 zend_hash_copy(Z_ARRVAL(headers), &message->hdrs, (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval *));
521 add_assoc_zval(&options, "headers", &headers);
522
523 /* check host header */
524 if (SUCCESS == zend_hash_find(&message->hdrs, "Host", sizeof("Host"), (void *) &zhost)) {
525 char *colon = NULL;
526 php_url parts, *url = php_url_parse(message->http.info.request.url);
527
528 memset(&parts, 0, sizeof(php_url));
529
530 /* check for port */
531 if ((colon = strchr(Z_STRVAL_PP(zhost), ':'))) {
532 parts.port = atoi(colon + 1);
533 parts.host = estrndup(Z_STRVAL_PP(zhost), (Z_STRVAL_PP(zhost) - colon - 1));
534 } else {
535 parts.host = estrndup(Z_STRVAL_PP(zhost), Z_STRLEN_PP(zhost));
536 }
537
538 http_build_url(HTTP_URL_REPLACE, url, &parts, NULL, &uri, NULL);
539 php_url_free(url);
540 efree(parts.host);
541 } else {
542 uri = http_absolute_url(message->http.info.request.url);
543 }
544
545 if ((request.meth = http_request_method_exists(1, 0, message->http.info.request.method))) {
546 http_request_body body;
547
548 http_request_init_ex(&request, NULL, request.meth, uri);
549 request.body = http_request_body_init_ex(&body, HTTP_REQUEST_BODY_CSTRING, PHPSTR_VAL(message), PHPSTR_LEN(message), 0);
550 if (SUCCESS == (rs = http_request_prepare(&request, NULL))) {
551 http_request_exec(&request);
552 }
553 http_request_dtor(&request);
554 } else {
555 http_error_ex(HE_WARNING, HTTP_E_REQUEST_METHOD,
556 "Cannot send HttpMessage. Request method %s not supported",
557 message->http.info.request.method);
558 }
559 efree(uri);
560 #else
561 http_error(HE_WARNING, HTTP_E_RUNTIME, "HTTP requests not supported - ext/http was not linked against libcurl.");
562 #endif
563 break;
564 }
565
566 case HTTP_MSG_NONE:
567 default:
568 http_error(HE_WARNING, HTTP_E_MESSAGE_TYPE, "HttpMessage is neither of type HTTP_MSG_REQUEST nor HTTP_MSG_RESPONSE");
569 break;
570 }
571
572 return rs;
573 }
574
575 PHP_HTTP_API http_message *_http_message_dup(http_message *orig TSRMLS_DC)
576 {
577 http_message *temp, *copy = NULL;
578 http_info info;
579
580 if (orig) {
581 info.type = orig->type;
582 info.http = orig->http;
583
584 copy = temp = http_message_new();
585 http_message_set_info(temp, &info);
586 zend_hash_copy(&temp->hdrs, &orig->hdrs, (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval *));
587 phpstr_append(&temp->body, orig->body.data, orig->body.used);
588
589 while (orig->parent) {
590 info.type = orig->parent->type;
591 info.http = orig->parent->http;
592
593 temp->parent = http_message_new();
594 http_message_set_info(temp->parent, &info);
595 zend_hash_copy(&temp->parent->hdrs, &orig->parent->hdrs, (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval *));
596 phpstr_append(&temp->parent->body, orig->parent->body.data, orig->parent->body.used);
597
598 temp = temp->parent;
599 orig = orig->parent;
600 }
601 }
602
603 return copy;
604 }
605
606 PHP_HTTP_API void _http_message_dtor(http_message *message)
607 {
608 if (message) {
609 zend_hash_destroy(&message->hdrs);
610 phpstr_dtor(PHPSTR(message));
611
612 switch (message->type) {
613 case HTTP_MSG_REQUEST:
614 STR_SET(message->http.info.request.method, NULL);
615 STR_SET(message->http.info.request.url, NULL);
616 break;
617
618 case HTTP_MSG_RESPONSE:
619 STR_SET(message->http.info.response.status, NULL);
620 break;
621
622 default:
623 break;
624 }
625 }
626 }
627
628 PHP_HTTP_API void _http_message_free(http_message **message)
629 {
630 if (*message) {
631 if ((*message)->parent) {
632 http_message_free(&(*message)->parent);
633 }
634 http_message_dtor(*message);
635 efree(*message);
636 *message = NULL;
637 }
638 }
639
640 /*
641 * Local variables:
642 * tab-width: 4
643 * c-basic-offset: 4
644 * End:
645 * vim600: noet sw=4 ts=4 fdm=marker
646 * vim<600: noet sw=4 ts=4
647 */
648