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