- only addref if we remember the stream
[m6w6/ext-http] / http_response_object.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
19 #ifdef HAVE_CONFIG_H
20 # include "config.h"
21 #endif
22 #include "php.h"
23
24 #include "missing.h"
25
26 /* broken static properties in PHP 5.0 */
27 #if defined(ZEND_ENGINE_2) && !defined(WONKY)
28
29 #include "SAPI.h"
30 #include "php_ini.h"
31
32 #include "php_http.h"
33 #include "php_http_api.h"
34 #include "php_http_std_defs.h"
35 #include "php_http_response_object.h"
36 #include "php_http_exception_object.h"
37 #include "php_http_send_api.h"
38 #include "php_http_cache_api.h"
39
40 ZEND_EXTERN_MODULE_GLOBALS(http);
41
42 #define GET_STATIC_PROP(n) *GET_STATIC_PROP_EX(http_response_object_ce, n)
43 #define UPD_STATIC_PROP(t, n, v) UPD_STATIC_PROP_EX(http_response_object_ce, t, n, v)
44 #define SET_STATIC_PROP(n, v) SET_STATIC_PROP_EX(http_response_object_ce, n, v)
45
46 #define HTTP_BEGIN_ARGS(method, req_args) HTTP_BEGIN_ARGS_EX(HttpResponse, method, 0, req_args)
47 #define HTTP_EMPTY_ARGS(method, ret_ref) HTTP_EMPTY_ARGS_EX(HttpResponse, method, ret_ref)
48 #define HTTP_RESPONSE_ME(method, visibility) PHP_ME(HttpResponse, method, HTTP_ARGS(HttpResponse, method), visibility|ZEND_ACC_STATIC)
49 #define HTTP_RESPONSE_ALIAS(method, func) HTTP_STATIC_ME_ALIAS(method, func, HTTP_ARGS(HttpResponse, method))
50
51 HTTP_BEGIN_ARGS(setHeader, 2)
52 HTTP_ARG_VAL(name, 0)
53 HTTP_ARG_VAL(value, 0)
54 HTTP_ARG_VAL(replace, 0)
55 HTTP_END_ARGS;
56
57 HTTP_BEGIN_ARGS(getHeader, 0)
58 HTTP_ARG_VAL(name, 0)
59 HTTP_END_ARGS;
60
61 HTTP_EMPTY_ARGS(getETag, 0);
62 HTTP_BEGIN_ARGS(setETag, 1)
63 HTTP_ARG_VAL(etag, 0)
64 HTTP_END_ARGS;
65
66 HTTP_EMPTY_ARGS(getLastModified, 0);
67 HTTP_BEGIN_ARGS(setLastModified, 1)
68 HTTP_ARG_VAL(timestamp, 0)
69 HTTP_END_ARGS;
70
71 HTTP_EMPTY_ARGS(getCache, 0);
72 HTTP_BEGIN_ARGS(setCache, 1)
73 HTTP_ARG_VAL(cache, 0)
74 HTTP_END_ARGS;
75
76 HTTP_EMPTY_ARGS(getGzip, 0);
77 HTTP_BEGIN_ARGS(setGzip, 1)
78 HTTP_ARG_VAL(gzip, 0)
79 HTTP_END_ARGS;
80
81 HTTP_EMPTY_ARGS(getCacheControl, 0);
82 HTTP_BEGIN_ARGS(setCacheControl, 1)
83 HTTP_ARG_VAL(cache_control, 0)
84 HTTP_ARG_VAL(max_age, 0)
85 HTTP_END_ARGS;
86
87 HTTP_EMPTY_ARGS(getContentType, 0);
88 HTTP_BEGIN_ARGS(setContentType, 1)
89 HTTP_ARG_VAL(content_type, 0)
90 HTTP_END_ARGS;
91
92 HTTP_BEGIN_ARGS(guessContentType, 1)
93 HTTP_ARG_VAL(magic_file, 0)
94 HTTP_ARG_VAL(magic_mode, 0)
95 HTTP_END_ARGS;
96
97 HTTP_EMPTY_ARGS(getContentDisposition, 0);
98 HTTP_BEGIN_ARGS(setContentDisposition, 1)
99 HTTP_ARG_VAL(filename, 0)
100 HTTP_ARG_VAL(send_inline, 0)
101 HTTP_END_ARGS;
102
103 HTTP_EMPTY_ARGS(getThrottleDelay, 0);
104 HTTP_BEGIN_ARGS(setThrottleDelay, 1)
105 HTTP_ARG_VAL(seconds, 0)
106 HTTP_END_ARGS;
107
108 HTTP_EMPTY_ARGS(getBufferSize, 0);
109 HTTP_BEGIN_ARGS(setBufferSize, 1)
110 HTTP_ARG_VAL(bytes, 0)
111 HTTP_END_ARGS;
112
113 HTTP_EMPTY_ARGS(getData, 0);
114 HTTP_BEGIN_ARGS(setData, 1)
115 HTTP_ARG_VAL(data, 0)
116 HTTP_END_ARGS;
117
118 HTTP_EMPTY_ARGS(getStream, 0);
119 HTTP_BEGIN_ARGS(setStream, 1)
120 HTTP_ARG_VAL(stream, 0)
121 HTTP_END_ARGS;
122
123 HTTP_EMPTY_ARGS(getFile, 0);
124 HTTP_BEGIN_ARGS(setFile, 1)
125 HTTP_ARG_VAL(filepath, 0)
126 HTTP_END_ARGS;
127
128 HTTP_BEGIN_ARGS(send, 0)
129 HTTP_ARG_VAL(clean_ob, 0)
130 HTTP_END_ARGS;
131
132 HTTP_EMPTY_ARGS(capture, 0);
133
134 HTTP_BEGIN_ARGS(redirect, 0)
135 HTTP_ARG_VAL(url, 0)
136 HTTP_ARG_VAL(params, 0)
137 HTTP_ARG_VAL(session, 0)
138 HTTP_ARG_VAL(permanent, 0)
139 HTTP_END_ARGS;
140
141 HTTP_BEGIN_ARGS(status, 1)
142 HTTP_ARG_VAL(code, 0)
143 HTTP_END_ARGS;
144
145 HTTP_EMPTY_ARGS(getRequestHeaders, 0);
146 HTTP_EMPTY_ARGS(getRequestBody, 0);
147
148 #define http_response_object_declare_default_properties() _http_response_object_declare_default_properties(TSRMLS_C)
149 static inline void _http_response_object_declare_default_properties(TSRMLS_D);
150
151 zend_class_entry *http_response_object_ce;
152 zend_function_entry http_response_object_fe[] = {
153
154 HTTP_RESPONSE_ME(setHeader, ZEND_ACC_PUBLIC)
155 HTTP_RESPONSE_ME(getHeader, ZEND_ACC_PUBLIC)
156
157 HTTP_RESPONSE_ME(setETag, ZEND_ACC_PUBLIC)
158 HTTP_RESPONSE_ME(getETag, ZEND_ACC_PUBLIC)
159
160 HTTP_RESPONSE_ME(setLastModified, ZEND_ACC_PUBLIC)
161 HTTP_RESPONSE_ME(getLastModified, ZEND_ACC_PUBLIC)
162
163 HTTP_RESPONSE_ME(setContentDisposition, ZEND_ACC_PUBLIC)
164 HTTP_RESPONSE_ME(getContentDisposition, ZEND_ACC_PUBLIC)
165
166 HTTP_RESPONSE_ME(setContentType, ZEND_ACC_PUBLIC)
167 HTTP_RESPONSE_ME(getContentType, ZEND_ACC_PUBLIC)
168
169 HTTP_RESPONSE_ME(guessContentType, ZEND_ACC_PUBLIC)
170
171 HTTP_RESPONSE_ME(setCache, ZEND_ACC_PUBLIC)
172 HTTP_RESPONSE_ME(getCache, ZEND_ACC_PUBLIC)
173
174 HTTP_RESPONSE_ME(setCacheControl, ZEND_ACC_PUBLIC)
175 HTTP_RESPONSE_ME(getCacheControl, ZEND_ACC_PUBLIC)
176
177 HTTP_RESPONSE_ME(setGzip, ZEND_ACC_PUBLIC)
178 HTTP_RESPONSE_ME(getGzip, ZEND_ACC_PUBLIC)
179
180 HTTP_RESPONSE_ME(setThrottleDelay, ZEND_ACC_PUBLIC)
181 HTTP_RESPONSE_ME(getThrottleDelay, ZEND_ACC_PUBLIC)
182
183 HTTP_RESPONSE_ME(setBufferSize, ZEND_ACC_PUBLIC)
184 HTTP_RESPONSE_ME(getBufferSize, ZEND_ACC_PUBLIC)
185
186 HTTP_RESPONSE_ME(setData, ZEND_ACC_PUBLIC)
187 HTTP_RESPONSE_ME(getData, ZEND_ACC_PUBLIC)
188
189 HTTP_RESPONSE_ME(setFile, ZEND_ACC_PUBLIC)
190 HTTP_RESPONSE_ME(getFile, ZEND_ACC_PUBLIC)
191
192 HTTP_RESPONSE_ME(setStream, ZEND_ACC_PUBLIC)
193 HTTP_RESPONSE_ME(getStream, ZEND_ACC_PUBLIC)
194
195 HTTP_RESPONSE_ME(send, ZEND_ACC_PUBLIC)
196 HTTP_RESPONSE_ME(capture, ZEND_ACC_PUBLIC)
197
198 HTTP_RESPONSE_ALIAS(redirect, http_redirect)
199 HTTP_RESPONSE_ALIAS(status, http_send_status)
200 HTTP_RESPONSE_ALIAS(getRequestHeaders, http_get_request_headers)
201 HTTP_RESPONSE_ALIAS(getRequestBody, http_get_request_body)
202
203 {NULL, NULL, NULL}
204 };
205
206 void _http_response_object_init(INIT_FUNC_ARGS)
207 {
208 HTTP_REGISTER_CLASS(HttpResponse, http_response_object, NULL, 0);
209 http_response_object_declare_default_properties();
210 }
211
212 static inline void _http_response_object_declare_default_properties(TSRMLS_D)
213 {
214 zend_class_entry *ce = http_response_object_ce;
215
216 DCL_STATIC_PROP(PRIVATE, bool, sent, 0);
217 DCL_STATIC_PROP(PRIVATE, bool, catch, 0);
218 DCL_STATIC_PROP(PRIVATE, long, mode, -1);
219 DCL_STATIC_PROP(PROTECTED, bool, cache, 0);
220 DCL_STATIC_PROP(PROTECTED, bool, gzip, 0);
221 DCL_STATIC_PROP(PROTECTED, long, stream, 0);
222 DCL_STATIC_PROP_N(PROTECTED, file);
223 DCL_STATIC_PROP_N(PROTECTED, data);
224 DCL_STATIC_PROP_N(PROTECTED, eTag);
225 DCL_STATIC_PROP(PROTECTED, long, lastModified, 0);
226 DCL_STATIC_PROP_N(PROTECTED, cacheControl);
227 DCL_STATIC_PROP_N(PROTECTED, contentType);
228 DCL_STATIC_PROP_N(PROTECTED, contentDisposition);
229 DCL_STATIC_PROP(PROTECTED, long, bufferSize, HTTP_SENDBUF_SIZE);
230 DCL_STATIC_PROP(PROTECTED, double, throttleDelay, 0.0);
231 DCL_STATIC_PROP_N(PROTECTED, headers);
232 }
233
234 /* ### USERLAND ### */
235
236 /* {{{ proto static bool HttpResponse::setHeader(string name, mixed value[, bool replace = true)
237 */
238 PHP_METHOD(HttpResponse, setHeader)
239 {
240 zend_bool replace = 1;
241 char *name;
242 int name_len = 0;
243 zval *value = NULL, *headers, **header;
244
245 if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz/!|b", &name, &name_len, &value, &replace)) {
246 RETURN_FALSE;
247 }
248 if (!name_len) {
249 http_error(HE_WARNING, HTTP_E_HEADER, "Cannot send anonymous headers");
250 RETURN_FALSE;
251 }
252
253 headers = GET_STATIC_PROP(headers);
254
255 if (Z_TYPE_P(headers) != IS_ARRAY) {
256 convert_to_array(headers);
257 }
258
259 /* delete header if value == null */
260 if (!value || Z_TYPE_P(value) == IS_NULL) {
261 RETURN_SUCCESS(zend_hash_del(Z_ARRVAL_P(headers), name, name_len + 1));
262 }
263
264 if (Z_TYPE_P(value) != IS_STRING) {
265 convert_to_string_ex(&value);
266 }
267
268 /* convert old header to an array and add new one if header exists and replace == false */
269 if (replace || (SUCCESS != zend_hash_find(Z_ARRVAL_P(headers), name, name_len + 1, (void **) &header))) {
270 RETURN_SUCCESS(add_assoc_stringl_ex(headers, name, name_len + 1, Z_STRVAL_P(value), Z_STRLEN_P(value), 1));
271 } else {
272 convert_to_array(*header);
273 RETURN_SUCCESS(add_next_index_stringl(*header, Z_STRVAL_P(value), Z_STRLEN_P(value), 1));
274 }
275 }
276 /* }}} */
277
278 /* {{{ proto static mixed HttpResponse::getHeader([string name])
279 */
280 PHP_METHOD(HttpResponse, getHeader)
281 {
282 char *name = NULL;
283 int name_len = 0;
284 zval *headers, **header;
285
286 if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s", &name, &name_len)) {
287 RETURN_FALSE;
288 }
289
290 headers = GET_STATIC_PROP(headers);
291 if (Z_TYPE_P(headers) != IS_ARRAY) {
292 convert_to_array(headers);
293 }
294
295 if (!name || !name_len) {
296 array_init(return_value);
297 array_copy(headers, return_value);
298 } else if (SUCCESS == zend_hash_find(Z_ARRVAL_P(headers), name, name_len + 1, (void **) &header)) {
299 RETURN_ZVAL(*header, 1, 0);
300 } else {
301 RETURN_NULL();
302 }
303 }
304 /* }}} */
305
306 /* {{{ proto static bool HttpResponse::setCache(bool cache)
307 *
308 * Whether it sould be attempted to cache the entitity.
309 * This will result in necessary caching headers and checks of clients
310 * "If-Modified-Since" and "If-None-Match" headers. If one of those headers
311 * matches a "304 Not Modified" status code will be issued.
312 *
313 * NOTE: If you're using sessions, be shure that you set session.cache_limiter
314 * to something more appropriate than "no-cache"!
315 */
316 PHP_METHOD(HttpResponse, setCache)
317 {
318 zend_bool do_cache = 0;
319
320 if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "b", &do_cache)) {
321 RETURN_FALSE;
322 }
323
324 RETURN_SUCCESS(UPD_STATIC_PROP(bool, cache, do_cache));
325 }
326 /* }}} */
327
328 /* {{{ proto static bool HttpResponse::getCache()
329 *
330 * Get current caching setting.
331 */
332 PHP_METHOD(HttpResponse, getCache)
333 {
334 NO_ARGS;
335
336 IF_RETVAL_USED {
337 RETURN_BOOL(Z_LVAL_P(GET_STATIC_PROP(cache)));
338 }
339 }
340 /* }}}*/
341
342 /* {{{ proto static bool HttpResponse::setGzip(bool gzip)
343 *
344 * Enable on-thy-fly gzipping of the sent entity.
345 */
346 PHP_METHOD(HttpResponse, setGzip)
347 {
348 zend_bool do_gzip = 0;
349
350 if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "b", &do_gzip)) {
351 RETURN_FALSE;
352 }
353
354 RETURN_SUCCESS(UPD_STATIC_PROP(bool, gzip, do_gzip));
355 }
356 /* }}} */
357
358 /* {{{ proto static bool HttpResponse::getGzip()
359 *
360 * Get current gzipping setting.
361 */
362 PHP_METHOD(HttpResponse, getGzip)
363 {
364 NO_ARGS;
365
366 IF_RETVAL_USED {
367 RETURN_BOOL(Z_LVAL_P(GET_STATIC_PROP(gzip)));
368 }
369 }
370 /* }}} */
371
372 /* {{{ proto static bool HttpResponse::setCacheControl(string control[, long max_age = 0])
373 *
374 * Set a custom cache-control header, usually being "private" or "public";
375 * The max_age parameter controls how long the cache entry is valid on the client side.
376 */
377 PHP_METHOD(HttpResponse, setCacheControl)
378 {
379 char *ccontrol, *cctl;
380 int cc_len;
381 long max_age = 0;
382
383 if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &ccontrol, &cc_len, &max_age)) {
384 RETURN_FALSE;
385 }
386
387 if (strcmp(ccontrol, "public") && strcmp(ccontrol, "private") && strcmp(ccontrol, "no-cache")) {
388 http_error_ex(HE_WARNING, HTTP_E_INVALID_PARAM, "Cache-Control '%s' doesn't match public, private or no-cache", ccontrol);
389 RETURN_FALSE;
390 } else {
391 spprintf(&cctl, 0, "%s, must-revalidate, max_age=%ld", ccontrol, max_age);
392 RETVAL_SUCCESS(UPD_STATIC_PROP(string, cacheControl, cctl));
393 efree(cctl);
394 }
395 }
396 /* }}} */
397
398 /* {{{ proto static string HttpResponse::getCacheControl()
399 *
400 * Get current Cache-Control header setting.
401 */
402 PHP_METHOD(HttpResponse, getCacheControl)
403 {
404 NO_ARGS;
405
406 IF_RETVAL_USED {
407 zval *ccontrol = GET_STATIC_PROP(cacheControl);
408 RETURN_STRINGL(Z_STRVAL_P(ccontrol), Z_STRLEN_P(ccontrol), 1);
409 }
410 }
411 /* }}} */
412
413 /* {{{ proto static bool HttpResponse::setContentType(string content_type)
414 *
415 * Set the content-type of the sent entity.
416 */
417 PHP_METHOD(HttpResponse, setContentType)
418 {
419 char *ctype;
420 int ctype_len;
421
422 if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &ctype, &ctype_len)) {
423 RETURN_FALSE;
424 }
425
426 if (!strchr(ctype, '/')) {
427 http_error_ex(HE_WARNING, HTTP_E_INVALID_PARAM, "Content type '%s' doesn't seem to contain a primary and a secondary part", ctype);
428 RETURN_FALSE;
429 }
430
431 RETURN_SUCCESS(UPD_STATIC_PROP(string, contentType, ctype));
432 }
433 /* }}} */
434
435 /* {{{ proto static string HttpResponse::getContentType()
436 *
437 * Get current Content-Type header setting.
438 */
439 PHP_METHOD(HttpResponse, getContentType)
440 {
441 NO_ARGS;
442
443 IF_RETVAL_USED {
444 zval *ctype = GET_STATIC_PROP(contentType);
445 RETURN_STRINGL(Z_STRVAL_P(ctype), Z_STRLEN_P(ctype), 1);
446 }
447 }
448 /* }}} */
449
450 /* {{{ proto static string HttpResponse::guessContentType(string magic_file[, long magic_mode])
451 *
452 * Attempts to guess the content type of supplied payload through libmagic.
453 * See docs/KnownIssues.txt!
454 */
455 PHP_METHOD(HttpResponse, guessContentType)
456 {
457 char *magic_file, *ct = NULL;
458 int magic_file_len;
459 long magic_mode = 0;
460
461 RETVAL_NULL();
462
463 SET_EH_THROW_HTTP();
464 if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &magic_file, &magic_file_len, &magic_mode)) {
465 switch (Z_LVAL_P(GET_STATIC_PROP(mode))) {
466 case SEND_DATA:
467 {
468 zval *data = GET_STATIC_PROP(data);
469 ct = http_guess_content_type(magic_file, magic_mode, Z_STRVAL_P(data), Z_STRLEN_P(data), SEND_DATA);
470 }
471 break;
472
473 case SEND_RSRC:
474 {
475 php_stream *s;
476 zval *z = GET_STATIC_PROP(stream);
477 z->type = IS_RESOURCE;
478 php_stream_from_zval(s, &z);
479 ct = http_guess_content_type(magic_file, magic_mode, s, 0, SEND_RSRC);
480 }
481 break;
482
483 default:
484 ct = http_guess_content_type(magic_file, magic_mode, Z_STRVAL_P(GET_STATIC_PROP(file)), 0, -1);
485 break;
486 }
487 if (ct) {
488 UPD_STATIC_PROP(string, contentType, ct);
489 RETVAL_STRING(ct, 0);
490 }
491 }
492 SET_EH_NORMAL();
493 }
494 /* }}} */
495
496 /* {{{ proto static bool HttpResponse::setContentDisposition(string filename[, bool inline = false])
497 *
498 * Set the Content-Disposition of the sent entity. This setting aims to suggest
499 * the receiveing user agent how to handle the sent entity; usually the client
500 * will show the user a "Save As..." popup.
501 */
502 PHP_METHOD(HttpResponse, setContentDisposition)
503 {
504 char *file, *cd;
505 int file_len;
506 zend_bool send_inline = 0;
507
508 if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|b", &file, &file_len, &send_inline)) {
509 RETURN_FALSE;
510 }
511
512 spprintf(&cd, 0, "%s; filename=\"%s\"", send_inline ? "inline" : "attachment", file);
513 RETVAL_SUCCESS(UPD_STATIC_PROP(string, contentDisposition, cd));
514 efree(cd);
515 }
516 /* }}} */
517
518 /* {{{ proto static string HttpResponse::getContentDisposition()
519 *
520 * Get current Content-Disposition setting.
521 */
522 PHP_METHOD(HttpResponse, getContentDisposition)
523 {
524 NO_ARGS;
525
526 IF_RETVAL_USED {
527 zval *cd = GET_STATIC_PROP(contentDisposition);
528 RETURN_STRINGL(Z_STRVAL_P(cd), Z_STRLEN_P(cd), 1);
529 }
530 }
531 /* }}} */
532
533 /* {{{ proto static bool HttpResponse::setETag(string etag)
534 *
535 * Set a custom ETag. Use this only if you know what you're doing.
536 */
537 PHP_METHOD(HttpResponse, setETag)
538 {
539 char *etag;
540 int etag_len;
541
542 if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &etag, &etag_len)) {
543 RETURN_FALSE;
544 }
545
546 RETURN_SUCCESS(UPD_STATIC_PROP(string, eTag, etag));
547 }
548 /* }}} */
549
550 /* {{{ proto static string HttpResponse::getETag()
551 *
552 * Get calculated or previously set custom ETag.
553 */
554 PHP_METHOD(HttpResponse, getETag)
555 {
556 NO_ARGS;
557
558 IF_RETVAL_USED {
559 zval *etag = GET_STATIC_PROP(eTag);
560 RETURN_STRINGL(Z_STRVAL_P(etag), Z_STRLEN_P(etag), 1);
561 }
562 }
563 /* }}} */
564
565 /* {{{ proto static bool HttpResponse::setLastModified(long timestamp)
566 *
567 * Set a custom Last-Modified date.
568 */
569 PHP_METHOD(HttpResponse, setLastModified)
570 {
571 long lm;
572
573 if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &lm)) {
574 RETURN_FALSE;
575 }
576
577 RETURN_SUCCESS(UPD_STATIC_PROP(long, lastModified, lm));
578 }
579 /* }}} */
580
581 /* {{{ proto static HttpResponse::getLastModified()
582 *
583 * Get calculated or previously set custom Last-Modified date.
584 */
585 PHP_METHOD(HttpResponse, getLastModified)
586 {
587 NO_ARGS;
588
589 IF_RETVAL_USED {
590 RETURN_LONG(Z_LVAL_P(GET_STATIC_PROP(lastModified)));
591 }
592 }
593 /* }}} */
594
595 /* {{{ proto static bool HttpResponse::setThrottleDelay(double seconds)
596 *
597 */
598 PHP_METHOD(HttpResponse, setThrottleDelay)
599 {
600 double seconds;
601
602 if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "d", &seconds)) {
603 RETURN_FALSE;
604 }
605 RETURN_SUCCESS(UPD_STATIC_PROP(double, throttleDelay, seconds));
606 }
607 /* }}} */
608
609 /* {{{ proto static double HttpResponse::getThrottleDelay()
610 *
611 */
612 PHP_METHOD(HttpResponse, getThrottleDelay)
613 {
614 NO_ARGS;
615
616 IF_RETVAL_USED {
617 RETURN_DOUBLE(Z_DVAL_P(GET_STATIC_PROP(throttleDelay)));
618 }
619 }
620 /* }}} */
621
622 /* {{{ proto static bool HttpResponse::setBufferSize(long bytes)
623 *
624 */
625 PHP_METHOD(HttpResponse, setBufferSize)
626 {
627 long bytes;
628
629 if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &bytes)) {
630 RETURN_FALSE;
631 }
632 RETURN_SUCCESS(UPD_STATIC_PROP(long, bufferSize, bytes));
633 }
634 /* }}} */
635
636 /* {{{ proto static long HttpResponse::getBufferSize()
637 *
638 */
639 PHP_METHOD(HttpResponse, getBufferSize)
640 {
641 NO_ARGS;
642
643 IF_RETVAL_USED {
644 RETURN_LONG(Z_LVAL_P(GET_STATIC_PROP(bufferSize)));
645 }
646 }
647 /* }}} */
648
649 /* {{{ proto static bool HttpResponse::setData(string data)
650 *
651 * Set the data to be sent.
652 */
653 PHP_METHOD(HttpResponse, setData)
654 {
655 zval *the_data;
656
657 if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &the_data)) {
658 RETURN_FALSE;
659 }
660 if (Z_TYPE_P(the_data) != IS_STRING) {
661 convert_to_string_ex(&the_data);
662 }
663
664 if ( (SUCCESS != SET_STATIC_PROP(data, the_data)) ||
665 (SUCCESS != UPD_STATIC_PROP(long, mode, SEND_DATA))) {
666 RETURN_FALSE;
667 }
668
669 if (!(Z_LVAL_P(GET_STATIC_PROP(lastModified)) > 0)) {
670 UPD_STATIC_PROP(long, lastModified, http_last_modified(the_data, SEND_DATA));
671 }
672 if (!Z_STRLEN_P(GET_STATIC_PROP(eTag))) {
673 char *etag = http_etag(Z_STRVAL_P(the_data), Z_STRLEN_P(the_data), SEND_DATA);
674 UPD_STATIC_PROP(string, eTag, etag);
675 efree(etag);
676 }
677
678 RETURN_TRUE;
679 }
680 /* }}} */
681
682 /* {{{ proto static string HttpResponse::getData()
683 *
684 * Get the previously set data to be sent.
685 */
686 PHP_METHOD(HttpResponse, getData)
687 {
688 NO_ARGS;
689
690 IF_RETVAL_USED {
691 zval *the_data = GET_STATIC_PROP(data);
692 RETURN_STRINGL(Z_STRVAL_P(the_data), Z_STRLEN_P(the_data), 1);
693 }
694 }
695 /* }}} */
696
697 /* {{{ proto static bool HttpResponse::setStream(resource stream)
698 *
699 * Set the resource to be sent.
700 */
701 PHP_METHOD(HttpResponse, setStream)
702 {
703 zval *the_stream;
704 php_stream *the_real_stream;
705 php_stream_statbuf ssb;
706
707 if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &the_stream)) {
708 RETURN_FALSE;
709 }
710
711 php_stream_from_zval(the_real_stream, &the_stream);
712 if (php_stream_stat(the_real_stream, &ssb)) {
713 RETURN_FALSE;
714 }
715
716 if ( (SUCCESS != UPD_STATIC_PROP(long, stream, Z_LVAL_P(the_stream))) ||
717 (SUCCESS != UPD_STATIC_PROP(long, mode, SEND_RSRC))) {
718 RETURN_FALSE;
719 }
720 zend_list_addref(Z_LVAL_P(the_stream));
721
722 if (!(Z_LVAL_P(GET_STATIC_PROP(lastModified)) > 0)) {
723 UPD_STATIC_PROP(long, lastModified, http_last_modified(the_real_stream, SEND_RSRC));
724 }
725 if (!Z_STRLEN_P(GET_STATIC_PROP(eTag))) {
726 char *etag = http_etag(the_real_stream, 0, SEND_RSRC);
727 UPD_STATIC_PROP(string, eTag, etag);
728 efree(etag);
729 }
730
731 RETURN_TRUE;
732 }
733 /* }}} */
734
735 /* {{{ proto static resource HttpResponse::getStream()
736 *
737 * Get the previously set resource to be sent.
738 */
739 PHP_METHOD(HttpResponse, getStream)
740 {
741 NO_ARGS;
742
743 IF_RETVAL_USED {
744 RETURN_RESOURCE(Z_LVAL_P(GET_STATIC_PROP(stream)));
745 }
746 }
747 /* }}} */
748
749 /* {{{ proto static bool HttpResponse::setFile(string file)
750 *
751 * Set the file to be sent.
752 */
753 PHP_METHOD(HttpResponse, setFile)
754 {
755 char *the_file;
756 int file_len;
757 php_stream_statbuf ssb;
758
759 if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &the_file, &file_len)) {
760 RETURN_FALSE;
761 }
762
763 if (php_stream_stat_path(the_file, &ssb)) {
764 RETURN_FALSE;
765 }
766
767 if ( (SUCCESS != UPD_STATIC_PROP(string, file, the_file)) ||
768 (SUCCESS != UPD_STATIC_PROP(long, mode, -1))) {
769 RETURN_FALSE;
770 }
771
772 if (!(Z_LVAL_P(GET_STATIC_PROP(lastModified)))) {
773 UPD_STATIC_PROP(long, lastModified, http_last_modified(the_file, -1));
774 }
775 if (!Z_STRLEN_P(GET_STATIC_PROP(eTag))) {
776 char *etag = http_etag(the_file, 0, -1);
777 UPD_STATIC_PROP(string, eTag, etag);
778 efree(etag);
779 }
780
781 RETURN_TRUE;
782 }
783 /* }}} */
784
785 /* {{{ proto static string HttpResponse::getFile()
786 *
787 * Get the previously set file to be sent.
788 */
789 PHP_METHOD(HttpResponse, getFile)
790 {
791 NO_ARGS;
792
793 IF_RETVAL_USED {
794 zval *the_file = GET_STATIC_PROP(file);
795 RETURN_STRINGL(Z_STRVAL_P(the_file), Z_STRLEN_P(the_file), 1);
796 }
797 }
798 /* }}} */
799
800 /* {{{ proto static bool HttpResponse::send([bool clean_ob = true])
801 *
802 * Finally send the entity.
803 *
804 * Example:
805 * <pre>
806 * <?php
807 * HttpResponse::setCache(true);
808 * HttpResponse::setContentType('application/pdf');
809 * HttpResponse::setContentDisposition("$user.pdf", false);
810 * HttpResponse::setFile('sheet.pdf');
811 * HttpResponse::send();
812 * ?>
813 * </pre>
814 */
815 PHP_METHOD(HttpResponse, send)
816 {
817 zval *sent, *headers;
818 zend_bool clean_ob = 1;
819
820 if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|b", &clean_ob)) {
821 RETURN_FALSE;
822 }
823 if (SG(headers_sent)) {
824 http_error(HE_WARNING, HTTP_E_RESPONSE, "Cannot send HttpResponse, headers have already been sent");
825 RETURN_FALSE;
826 }
827
828 sent = GET_STATIC_PROP(sent);
829 if (Z_LVAL_P(sent)) {
830 http_error(HE_WARNING, HTTP_E_RESPONSE, "Cannot send HttpResponse, response has already been sent");
831 RETURN_FALSE;
832 } else {
833 Z_LVAL_P(sent) = 1;
834 }
835
836 /* capture mode */
837 if (Z_BVAL_P(GET_STATIC_PROP(catch))) {
838 zval *the_data;
839
840 MAKE_STD_ZVAL(the_data);
841 php_ob_get_buffer(the_data TSRMLS_CC);
842 SET_STATIC_PROP(data, the_data);
843 ZVAL_LONG(GET_STATIC_PROP(mode), SEND_DATA);
844
845 if (!Z_STRLEN_P(GET_STATIC_PROP(eTag))) {
846 char *etag = http_etag(Z_STRVAL_P(the_data), Z_STRLEN_P(the_data), SEND_DATA);
847 UPD_STATIC_PROP(string, eTag, etag);
848 efree(etag);
849 }
850 zval_ptr_dtor(&the_data);
851
852 clean_ob = 1;
853 }
854
855 if (clean_ob) {
856 /* interrupt on-the-fly etag generation */
857 HTTP_G(etag).started = 0;
858 /* discard previous output buffers */
859 php_end_ob_buffers(0 TSRMLS_CC);
860 }
861
862 /* custom headers */
863 headers = GET_STATIC_PROP(headers);
864 if (Z_TYPE_P(headers) == IS_ARRAY) {
865 char *name = NULL;
866 ulong idx = 0;
867 zval **value;
868
869 FOREACH_KEYVAL(headers, name, idx, value) {
870 if (name) {
871 if (Z_TYPE_PP(value) == IS_ARRAY) {
872 zend_bool first = 1;
873 zval **data;
874
875 FOREACH_VAL(*value, data) {
876 http_send_header_ex(name, strlen(name), Z_STRVAL_PP(data), Z_STRLEN_PP(data), first, NULL);
877 first = 0;
878 }
879 } else {
880 http_send_header_ex(name, strlen(name), Z_STRVAL_PP(value), Z_STRLEN_PP(value), 1, NULL);
881 }
882 name = NULL;
883 }
884 }
885 }
886
887 /* gzip */
888 if (Z_LVAL_P(GET_STATIC_PROP(gzip))) {
889 php_start_ob_buffer_named("ob_gzhandler", 0, 0 TSRMLS_CC);
890 } else {
891 php_start_ob_buffer(NULL, 0, 0 TSRMLS_CC);
892 }
893
894 /* caching */
895 if (Z_LVAL_P(GET_STATIC_PROP(cache))) {
896 zval *cctl, *etag, *lmod;
897
898 etag = GET_STATIC_PROP(eTag);
899 lmod = GET_STATIC_PROP(lastModified);
900 cctl = GET_STATIC_PROP(cacheControl);
901
902 http_cache_etag(Z_STRVAL_P(etag), Z_STRLEN_P(etag), Z_STRVAL_P(cctl), Z_STRLEN_P(cctl));
903 http_cache_last_modified(Z_LVAL_P(lmod), Z_LVAL_P(lmod) ? Z_LVAL_P(lmod) : time(NULL), Z_STRVAL_P(cctl), Z_STRLEN_P(cctl));
904 }
905
906 /* content type */
907 {
908 zval *ctype = GET_STATIC_PROP(contentType);
909 if (Z_STRLEN_P(ctype)) {
910 http_send_content_type(Z_STRVAL_P(ctype), Z_STRLEN_P(ctype));
911 } else {
912 char *ctypes = INI_STR("default_mimetype");
913 size_t ctlen = ctypes ? strlen(ctypes) : 0;
914
915 if (ctlen) {
916 http_send_content_type(ctypes, ctlen);
917 } else {
918 http_send_content_type("application/x-octetstream", lenof("application/x-octetstream"));
919 }
920 }
921 }
922
923 /* content disposition */
924 {
925 zval *cd = GET_STATIC_PROP(contentDisposition);
926 if (Z_STRLEN_P(cd)) {
927 http_send_header_ex("Content-Disposition", lenof("Content-Disposition"), Z_STRVAL_P(cd), Z_STRLEN_P(cd), 1, NULL);
928 }
929 }
930
931 /* throttling */
932 {
933 HTTP_G(send).buffer_size = Z_LVAL_P(GET_STATIC_PROP(bufferSize));
934 HTTP_G(send).throttle_delay = Z_DVAL_P(GET_STATIC_PROP(throttleDelay));
935 }
936
937 /* send */
938 {
939 switch (Z_LVAL_P(GET_STATIC_PROP(mode)))
940 {
941 case SEND_DATA:
942 {
943 zval *zdata = GET_STATIC_PROP(data);
944 RETURN_SUCCESS(http_send_data_ex(Z_STRVAL_P(zdata), Z_STRLEN_P(zdata), 1));
945 }
946
947 case SEND_RSRC:
948 {
949 php_stream *the_real_stream;
950 zval *the_stream = GET_STATIC_PROP(stream);
951 the_stream->type = IS_RESOURCE;
952 php_stream_from_zval(the_real_stream, &the_stream);
953 RETURN_SUCCESS(http_send_stream_ex(the_real_stream, 0, 1));
954 }
955
956 default:
957 {
958 RETURN_SUCCESS(http_send_file_ex(Z_STRVAL_P(GET_STATIC_PROP(file)), 1));
959 }
960 }
961 }
962 }
963 /* }}} */
964
965 /* {{{ proto static void HttpResponse::capture()
966 *
967 * Capture script output.
968 *
969 * Example:
970 * <pre>
971 * <?php
972 * HttpResponse::setCache(true);
973 * HttpResponse::capture();
974 * // script follows
975 * ?>
976 * </pre>
977 */
978 PHP_METHOD(HttpResponse, capture)
979 {
980 zval do_catch;
981
982 NO_ARGS;
983
984 INIT_PZVAL(&do_catch);
985 ZVAL_LONG(&do_catch, 1);
986
987 SET_STATIC_PROP(catch, &do_catch);
988
989 php_end_ob_buffers(0 TSRMLS_CC);
990 php_start_ob_buffer(NULL, 0, 0 TSRMLS_CC);
991
992 #ifndef WONKY
993 /* register shutdown function --
994 messing around with ob and headers only works in PHP-5.1 or greater */
995 {
996 zval func, retval, arg, *argp[1];
997
998 INIT_PZVAL(&arg);
999 INIT_PZVAL(&func);
1000 INIT_PZVAL(&retval);
1001 ZVAL_STRINGL(&func, "register_shutdown_function", lenof("register_shutdown_function"), 0);
1002
1003 array_init(&arg);
1004 add_next_index_stringl(&arg, "HttpResponse", lenof("HttpResponse"), 1);
1005 add_next_index_stringl(&arg, "send", lenof("send"), 1);
1006 argp[0] = &arg;
1007 call_user_function(EG(function_table), NULL, &func, &retval, 1, argp TSRMLS_CC);
1008 zval_dtor(&arg);
1009 }
1010 #endif
1011 }
1012 /* }}} */
1013
1014 #endif /* ZEND_ENGINE_2 && !WONKY */
1015
1016 /*
1017 * Local variables:
1018 * tab-width: 4
1019 * c-basic-offset: 4
1020 * End:
1021 * vim600: noet sw=4 ts=4 fdm=marker
1022 * vim<600: noet sw=4 ts=4
1023 */
1024