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