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