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