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