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