- drop mhash support
[m6w6/ext-http] / http_response_object.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
16 #ifdef HAVE_CONFIG_H
17 # include "config.h"
18 #endif
19 #include "php.h"
20
21 #include "missing.h"
22
23 /* broken static properties in PHP 5.0 */
24 #if defined(ZEND_ENGINE_2) && !defined(WONKY)
25
26 #include "SAPI.h"
27 #include "php_ini.h"
28
29 #include "php_http.h"
30 #include "php_http_api.h"
31 #include "php_http_std_defs.h"
32 #include "php_http_response_object.h"
33 #include "php_http_exception_object.h"
34 #include "php_http_send_api.h"
35 #include "php_http_cache_api.h"
36 #include "php_http_headers_api.h"
37
38 #ifdef HTTP_HAVE_MAGIC
39 # include <magic.h>
40 #endif
41
42 ZEND_EXTERN_MODULE_GLOBALS(http);
43
44 #define GET_STATIC_PROP(n) *GET_STATIC_PROP_EX(http_response_object_ce, n)
45 #define UPD_STATIC_PROP(t, n, v) UPD_STATIC_PROP_EX(http_response_object_ce, t, n, v)
46 #define SET_STATIC_PROP(n, v) SET_STATIC_PROP_EX(http_response_object_ce, n, v)
47 #define UPD_STATIC_STRL(n, v, l) UPD_STATIC_STRL_EX(http_response_object_ce, n, v, l)
48
49 #define HTTP_BEGIN_ARGS(method, req_args) HTTP_BEGIN_ARGS_EX(HttpResponse, method, 0, req_args)
50 #define HTTP_EMPTY_ARGS(method, ret_ref) HTTP_EMPTY_ARGS_EX(HttpResponse, method, ret_ref)
51 #define HTTP_RESPONSE_ME(method, visibility) PHP_ME(HttpResponse, method, HTTP_ARGS(HttpResponse, method), visibility|ZEND_ACC_STATIC)
52 #define HTTP_RESPONSE_ALIAS(method, func) HTTP_STATIC_ME_ALIAS(method, func, HTTP_ARGS(HttpResponse, method))
53
54 HTTP_BEGIN_ARGS(setHeader, 2)
55 HTTP_ARG_VAL(name, 0)
56 HTTP_ARG_VAL(value, 0)
57 HTTP_ARG_VAL(replace, 0)
58 HTTP_END_ARGS;
59
60 HTTP_BEGIN_ARGS(getHeader, 0)
61 HTTP_ARG_VAL(name, 0)
62 HTTP_END_ARGS;
63
64 HTTP_EMPTY_ARGS(getETag, 0);
65 HTTP_BEGIN_ARGS(setETag, 1)
66 HTTP_ARG_VAL(etag, 0)
67 HTTP_END_ARGS;
68
69 HTTP_EMPTY_ARGS(getLastModified, 0);
70 HTTP_BEGIN_ARGS(setLastModified, 1)
71 HTTP_ARG_VAL(timestamp, 0)
72 HTTP_END_ARGS;
73
74 HTTP_EMPTY_ARGS(getCache, 0);
75 HTTP_BEGIN_ARGS(setCache, 1)
76 HTTP_ARG_VAL(cache, 0)
77 HTTP_END_ARGS;
78
79 HTTP_EMPTY_ARGS(getGzip, 0);
80 HTTP_BEGIN_ARGS(setGzip, 1)
81 HTTP_ARG_VAL(gzip, 0)
82 HTTP_END_ARGS;
83
84 HTTP_EMPTY_ARGS(getCacheControl, 0);
85 HTTP_BEGIN_ARGS(setCacheControl, 1)
86 HTTP_ARG_VAL(cache_control, 0)
87 HTTP_ARG_VAL(max_age, 0)
88 HTTP_END_ARGS;
89
90 HTTP_EMPTY_ARGS(getContentType, 0);
91 HTTP_BEGIN_ARGS(setContentType, 1)
92 HTTP_ARG_VAL(content_type, 0)
93 HTTP_END_ARGS;
94
95 HTTP_BEGIN_ARGS(guessContentType, 1)
96 HTTP_ARG_VAL(magic_file, 0)
97 HTTP_ARG_VAL(magic_mode, 0)
98 HTTP_END_ARGS;
99
100 HTTP_EMPTY_ARGS(getContentDisposition, 0);
101 HTTP_BEGIN_ARGS(setContentDisposition, 1)
102 HTTP_ARG_VAL(filename, 0)
103 HTTP_ARG_VAL(send_inline, 0)
104 HTTP_END_ARGS;
105
106 HTTP_EMPTY_ARGS(getThrottleDelay, 0);
107 HTTP_BEGIN_ARGS(setThrottleDelay, 1)
108 HTTP_ARG_VAL(seconds, 0)
109 HTTP_END_ARGS;
110
111 HTTP_EMPTY_ARGS(getBufferSize, 0);
112 HTTP_BEGIN_ARGS(setBufferSize, 1)
113 HTTP_ARG_VAL(bytes, 0)
114 HTTP_END_ARGS;
115
116 HTTP_EMPTY_ARGS(getData, 0);
117 HTTP_BEGIN_ARGS(setData, 1)
118 HTTP_ARG_VAL(data, 0)
119 HTTP_END_ARGS;
120
121 HTTP_EMPTY_ARGS(getStream, 0);
122 HTTP_BEGIN_ARGS(setStream, 1)
123 HTTP_ARG_VAL(stream, 0)
124 HTTP_END_ARGS;
125
126 HTTP_EMPTY_ARGS(getFile, 0);
127 HTTP_BEGIN_ARGS(setFile, 1)
128 HTTP_ARG_VAL(filepath, 0)
129 HTTP_END_ARGS;
130
131 HTTP_BEGIN_ARGS(send, 0)
132 HTTP_ARG_VAL(clean_ob, 0)
133 HTTP_END_ARGS;
134
135 HTTP_EMPTY_ARGS(capture, 0);
136
137 HTTP_BEGIN_ARGS(redirect, 0)
138 HTTP_ARG_VAL(url, 0)
139 HTTP_ARG_VAL(params, 0)
140 HTTP_ARG_VAL(session, 0)
141 HTTP_ARG_VAL(permanent, 0)
142 HTTP_END_ARGS;
143
144 HTTP_BEGIN_ARGS(status, 1)
145 HTTP_ARG_VAL(code, 0)
146 HTTP_END_ARGS;
147
148 HTTP_EMPTY_ARGS(getRequestHeaders, 0);
149 HTTP_EMPTY_ARGS(getRequestBody, 0);
150
151 #define http_response_object_declare_default_properties() _http_response_object_declare_default_properties(TSRMLS_C)
152 static inline void _http_response_object_declare_default_properties(TSRMLS_D);
153 #define http_grab_response_headers _http_grab_response_headers
154 static void _http_grab_response_headers(void *data, void *arg TSRMLS_DC);
155
156 zend_class_entry *http_response_object_ce;
157 zend_function_entry http_response_object_fe[] = {
158
159 HTTP_RESPONSE_ME(setHeader, ZEND_ACC_PUBLIC)
160 HTTP_RESPONSE_ME(getHeader, ZEND_ACC_PUBLIC)
161
162 HTTP_RESPONSE_ME(setETag, ZEND_ACC_PUBLIC)
163 HTTP_RESPONSE_ME(getETag, ZEND_ACC_PUBLIC)
164
165 HTTP_RESPONSE_ME(setLastModified, ZEND_ACC_PUBLIC)
166 HTTP_RESPONSE_ME(getLastModified, ZEND_ACC_PUBLIC)
167
168 HTTP_RESPONSE_ME(setContentDisposition, ZEND_ACC_PUBLIC)
169 HTTP_RESPONSE_ME(getContentDisposition, ZEND_ACC_PUBLIC)
170
171 HTTP_RESPONSE_ME(setContentType, ZEND_ACC_PUBLIC)
172 HTTP_RESPONSE_ME(getContentType, ZEND_ACC_PUBLIC)
173
174 HTTP_RESPONSE_ME(guessContentType, ZEND_ACC_PUBLIC)
175
176 HTTP_RESPONSE_ME(setCache, ZEND_ACC_PUBLIC)
177 HTTP_RESPONSE_ME(getCache, ZEND_ACC_PUBLIC)
178
179 HTTP_RESPONSE_ME(setCacheControl, ZEND_ACC_PUBLIC)
180 HTTP_RESPONSE_ME(getCacheControl, ZEND_ACC_PUBLIC)
181
182 HTTP_RESPONSE_ME(setGzip, ZEND_ACC_PUBLIC)
183 HTTP_RESPONSE_ME(getGzip, ZEND_ACC_PUBLIC)
184
185 HTTP_RESPONSE_ME(setThrottleDelay, ZEND_ACC_PUBLIC)
186 HTTP_RESPONSE_ME(getThrottleDelay, ZEND_ACC_PUBLIC)
187
188 HTTP_RESPONSE_ME(setBufferSize, ZEND_ACC_PUBLIC)
189 HTTP_RESPONSE_ME(getBufferSize, ZEND_ACC_PUBLIC)
190
191 HTTP_RESPONSE_ME(setData, ZEND_ACC_PUBLIC)
192 HTTP_RESPONSE_ME(getData, ZEND_ACC_PUBLIC)
193
194 HTTP_RESPONSE_ME(setFile, ZEND_ACC_PUBLIC)
195 HTTP_RESPONSE_ME(getFile, ZEND_ACC_PUBLIC)
196
197 HTTP_RESPONSE_ME(setStream, ZEND_ACC_PUBLIC)
198 HTTP_RESPONSE_ME(getStream, ZEND_ACC_PUBLIC)
199
200 HTTP_RESPONSE_ME(send, ZEND_ACC_PUBLIC)
201 HTTP_RESPONSE_ME(capture, ZEND_ACC_PUBLIC)
202
203 HTTP_RESPONSE_ALIAS(redirect, http_redirect)
204 HTTP_RESPONSE_ALIAS(status, http_send_status)
205 HTTP_RESPONSE_ALIAS(getRequestHeaders, http_get_request_headers)
206 HTTP_RESPONSE_ALIAS(getRequestBody, http_get_request_body)
207
208 EMPTY_FUNCTION_ENTRY
209 };
210
211 PHP_MINIT_FUNCTION(http_response_object)
212 {
213 HTTP_REGISTER_CLASS(HttpResponse, http_response_object, NULL, 0);
214 http_response_object_declare_default_properties();
215 return SUCCESS;
216 }
217
218 static inline void _http_response_object_declare_default_properties(TSRMLS_D)
219 {
220 zend_class_entry *ce = http_response_object_ce;
221
222 DCL_STATIC_PROP(PRIVATE, bool, sent, 0);
223 DCL_STATIC_PROP(PRIVATE, bool, catch, 0);
224 DCL_STATIC_PROP(PRIVATE, long, mode, -1);
225 DCL_STATIC_PROP(PRIVATE, long, stream, 0);
226 DCL_STATIC_PROP_N(PRIVATE, file);
227 DCL_STATIC_PROP_N(PRIVATE, data);
228 DCL_STATIC_PROP(PROTECTED, bool, cache, 0);
229 DCL_STATIC_PROP(PROTECTED, bool, gzip, 0);
230 DCL_STATIC_PROP_N(PROTECTED, eTag);
231 DCL_STATIC_PROP(PROTECTED, long, lastModified, 0);
232 DCL_STATIC_PROP_N(PROTECTED, cacheControl);
233 DCL_STATIC_PROP_N(PROTECTED, contentType);
234 DCL_STATIC_PROP_N(PROTECTED, contentDisposition);
235 DCL_STATIC_PROP(PROTECTED, long, bufferSize, HTTP_SENDBUF_SIZE);
236 DCL_STATIC_PROP(PROTECTED, double, throttleDelay, 0.0);
237
238 #ifndef WONKY
239 DCL_CONST(long, "REDIRECT", HTTP_REDIRECT);
240 DCL_CONST(long, "REDIRECT_PERM", HTTP_REDIRECT_PERM);
241 DCL_CONST(long, "REDIRECT_POST", HTTP_REDIRECT_POST);
242 DCL_CONST(long, "REDIRECT_TEMP", HTTP_REDIRECT_TEMP);
243 #endif /* WONKY */
244 }
245
246 static void _http_grab_response_headers(void *data, void *arg TSRMLS_DC)
247 {
248 phpstr_appendl(PHPSTR(arg), ((sapi_header_struct *)data)->header);
249 phpstr_appends(PHPSTR(arg), HTTP_CRLF);
250 }
251
252 /* ### USERLAND ### */
253
254 /* {{{ proto static bool HttpResponse::setHeader(string name, mixed value[, bool replace = true])
255 *
256 * Send an HTTP header.
257 *
258 * Expects a string parameter containing the name of the header and a mixed
259 * parameter containing the value of the header, which will be converted to
260 * a string. Additionally accepts an optional boolean parameter, which
261 * specifies whether an existing header should be replaced. If the second
262 * parameter is unset no header with this name will be sent.
263 *
264 * Returns TRUE on success, or FALSE on failure.
265 *
266 * Throws HttpHeaderException if http.only_exceptions is TRUE.
267 */
268 PHP_METHOD(HttpResponse, setHeader)
269 {
270 zend_bool replace = 1;
271 char *name;
272 int name_len = 0;
273 zval *value = NULL, *orig = NULL;
274
275 if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz/!|b", &name, &name_len, &value, &replace)) {
276 RETURN_FALSE;
277 }
278 if (SG(headers_sent)) {
279 http_error(HE_WARNING, HTTP_E_HEADER, "Cannot add another header when headers have already been sent");
280 RETURN_FALSE;
281 }
282 if (!name_len) {
283 http_error(HE_WARNING, HTTP_E_HEADER, "Cannot send anonymous headers");
284 RETURN_FALSE;
285 }
286
287 /* delete header if value == null */
288 if (!value || Z_TYPE_P(value) == IS_NULL) {
289 RETURN_SUCCESS(http_send_header_ex(name, name_len, "", 0, replace, NULL));
290 }
291 /* send multiple header if replace is false and value is an array */
292 if (!replace && Z_TYPE_P(value) == IS_ARRAY) {
293 zval **data;
294 HashPosition pos;
295
296 FOREACH_VAL(pos, value, data) {
297 zval *orig = *data;
298
299 convert_to_string_ex(data);
300 if (SUCCESS != http_send_header_ex(name, name_len, Z_STRVAL_PP(data), Z_STRLEN_PP(data), 0, NULL)) {
301 if (orig != *data) {
302 zval_ptr_dtor(data);
303 }
304 RETURN_FALSE;
305 }
306 if (orig != *data) {
307 zval_ptr_dtor(data);
308 }
309 }
310 RETURN_TRUE;
311 }
312 /* send standard header */
313 orig = value;
314 convert_to_string_ex(&value);
315 RETVAL_SUCCESS(http_send_header_ex(name, name_len, Z_STRVAL_P(value), Z_STRLEN_P(value), replace, NULL));
316 if (orig != value) {
317 zval_ptr_dtor(&value);
318 }
319 }
320 /* }}} */
321
322 /* {{{ proto static mixed HttpResponse::getHeader([string name])
323 *
324 * Get header(s) about to be sent.
325 *
326 * Accepts a string as optional parameter which specifies the name of the
327 * header to read. If the parameter is empty or omitted, an associative array
328 * with all headers will be returned.
329 *
330 * Returns either a string containing the value of the header matching name,
331 * FALSE on failure, or an associative array with all headers.
332 */
333 PHP_METHOD(HttpResponse, getHeader)
334 {
335 char *name = NULL;
336 int name_len = 0;
337 phpstr headers;
338
339 if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s", &name, &name_len)) {
340 RETURN_FALSE;
341 }
342
343 phpstr_init(&headers);
344 zend_llist_apply_with_argument(&SG(sapi_headers).headers, http_grab_response_headers, &headers TSRMLS_CC);
345 phpstr_fix(&headers);
346
347 if (name && name_len) {
348 zval **header;
349 HashTable headers_ht;
350
351 zend_hash_init(&headers_ht, sizeof(zval *), NULL, ZVAL_PTR_DTOR, 0);
352 if ( (SUCCESS == http_parse_headers_ex(PHPSTR_VAL(&headers), &headers_ht, 1)) &&
353 (SUCCESS == zend_hash_find(&headers_ht, name, name_len + 1, (void **) &header))) {
354 RETVAL_ZVAL(*header, 1, 0);
355 } else {
356 RETVAL_NULL();
357 }
358 zend_hash_destroy(&headers_ht);
359 } else {
360 array_init(return_value);
361 http_parse_headers_ex(PHPSTR_VAL(&headers), Z_ARRVAL_P(return_value), 1);
362 }
363
364 phpstr_dtor(&headers);
365 }
366 /* }}} */
367
368 /* {{{ proto static bool HttpResponse::setCache(bool cache)
369 *
370 * Whether it sould be attempted to cache the entitity.
371 * This will result in necessary caching headers and checks of clients
372 * "If-Modified-Since" and "If-None-Match" headers. If one of those headers
373 * matches a "304 Not Modified" status code will be issued.
374 *
375 * NOTE: If you're using sessions, be shure that you set session.cache_limiter
376 * to something more appropriate than "no-cache"!
377 *
378 * Expects a boolean as parameter specifying whether caching should be attempted.
379 *
380 * Returns TRUE ons success, or FALSE on failure.
381 */
382 PHP_METHOD(HttpResponse, setCache)
383 {
384 zend_bool do_cache = 0;
385
386 if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "b", &do_cache)) {
387 RETURN_FALSE;
388 }
389
390 RETURN_SUCCESS(UPD_STATIC_PROP(bool, cache, do_cache));
391 }
392 /* }}} */
393
394 /* {{{ proto static bool HttpResponse::getCache()
395 *
396 * Get current caching setting.
397 *
398 * Returns TRUE if caching should be attempted, else FALSE.
399 */
400 PHP_METHOD(HttpResponse, getCache)
401 {
402 NO_ARGS;
403
404 IF_RETVAL_USED {
405 zval *cache_p, *cache = convert_to_type_ex(IS_BOOL, GET_STATIC_PROP(cache), &cache_p);
406
407 RETVAL_ZVAL(cache, 1, 0);
408
409 if (cache_p) {
410 zval_ptr_dtor(&cache_p);
411 }
412 }
413 }
414 /* }}}*/
415
416 /* {{{ proto static bool HttpResponse::setGzip(bool gzip)
417 *
418 * Enable on-thy-fly gzipping of the sent entity.
419 *
420 * Expects a boolean as parameter indicating if GZip compression should be enabled.
421 *
422 * Returns TRUE on success, or FALSE on failure.
423 */
424 PHP_METHOD(HttpResponse, setGzip)
425 {
426 zend_bool do_gzip = 0;
427
428 if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "b", &do_gzip)) {
429 RETURN_FALSE;
430 }
431
432 RETURN_SUCCESS(UPD_STATIC_PROP(bool, gzip, do_gzip));
433 }
434 /* }}} */
435
436 /* {{{ proto static bool HttpResponse::getGzip()
437 *
438 * Get current gzipping setting.
439 *
440 * Returns TRUE if GZip compression is enabled, else FALSE.
441 */
442 PHP_METHOD(HttpResponse, getGzip)
443 {
444 NO_ARGS;
445
446 IF_RETVAL_USED {
447 zval *gzip_p, *gzip = convert_to_type_ex(IS_BOOL, GET_STATIC_PROP(gzip), &gzip_p);
448
449 RETVAL_ZVAL(gzip, 1, 0);
450
451 if (gzip_p) {
452 zval_ptr_dtor(&gzip_p);
453 }
454 }
455 }
456 /* }}} */
457
458 /* {{{ proto static bool HttpResponse::setCacheControl(string control[, int max_age = 0])
459 *
460 * Set a custom cache-control header, usually being "private" or "public";
461 * The max_age parameter controls how long the cache entry is valid on the client side.
462 *
463 * Expects a string parameter containing the primary cache control setting.
464 * Addtitionally accepts an int parameter specifying the max-age setting.
465 *
466 * Returns TRUE on success, or FALSE if control does not match one of
467 * "public" , "private" or "no-cache".
468 *
469 * Throws HttpInvalidParamException if http.only_exceptions is TRUE.
470 */
471 PHP_METHOD(HttpResponse, setCacheControl)
472 {
473 char *ccontrol, *cctl;
474 int cc_len;
475 long max_age = 0;
476
477 if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &ccontrol, &cc_len, &max_age)) {
478 RETURN_FALSE;
479 }
480
481 if (strcmp(ccontrol, "public") && strcmp(ccontrol, "private") && strcmp(ccontrol, "no-cache")) {
482 http_error_ex(HE_WARNING, HTTP_E_INVALID_PARAM, "Cache-Control '%s' doesn't match public, private or no-cache", ccontrol);
483 RETURN_FALSE;
484 } else {
485 size_t cctl_len = spprintf(&cctl, 0, "%s, must-revalidate, max-age=%ld", ccontrol, max_age);
486 RETVAL_SUCCESS(UPD_STATIC_STRL(cacheControl, cctl, cctl_len));
487 efree(cctl);
488 }
489 }
490 /* }}} */
491
492 /* {{{ proto static string HttpResponse::getCacheControl()
493 *
494 * Get current Cache-Control header setting.
495 *
496 * Returns the current cache control setting as a string like sent in a header.
497 */
498 PHP_METHOD(HttpResponse, getCacheControl)
499 {
500 NO_ARGS;
501
502 IF_RETVAL_USED {
503 zval *ccontrol_p, *ccontrol = convert_to_type_ex(IS_STRING, GET_STATIC_PROP(cacheControl), &ccontrol_p);
504
505 RETVAL_ZVAL(ccontrol, 1, 0);
506
507 if (ccontrol_p) {
508 zval_ptr_dtor(&ccontrol_p);
509 }
510 }
511 }
512 /* }}} */
513
514 /* {{{ proto static bool HttpResponse::setContentType(string content_type)
515 *
516 * Set the content-type of the sent entity.
517 *
518 * Expects a string as parameter specifying the content type of the sent entity.
519 *
520 * Returns TRUE on success, or FALSE if the content type does not seem to
521 * contain a primary and secondary content type part.
522 *
523 * Throws HttpInvalidParamException if http.only_exceptions is TRUE.
524 */
525 PHP_METHOD(HttpResponse, setContentType)
526 {
527 char *ctype;
528 int ctype_len;
529
530 if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &ctype, &ctype_len)) {
531 RETURN_FALSE;
532 }
533
534 HTTP_CHECK_CONTENT_TYPE(ctype, RETURN_FALSE);
535 RETURN_SUCCESS(UPD_STATIC_STRL(contentType, ctype, ctype_len));
536 }
537 /* }}} */
538
539 /* {{{ proto static string HttpResponse::getContentType()
540 *
541 * Get current Content-Type header setting.
542 *
543 * Returns the currently set content type as string.
544 */
545 PHP_METHOD(HttpResponse, getContentType)
546 {
547 NO_ARGS;
548
549 IF_RETVAL_USED {
550 zval *ctype_p, *ctype = convert_to_type_ex(IS_STRING, GET_STATIC_PROP(contentType), &ctype_p);
551
552 RETVAL_ZVAL(ctype, 1, 0);
553
554 if (ctype_p) {
555 zval_ptr_dtor(&ctype_p);
556 }
557 }
558 }
559 /* }}} */
560
561 /* {{{ proto static string HttpResponse::guessContentType(string magic_file[, int magic_mode = MAGIC_MIME])
562 *
563 * Attempts to guess the content type of supplied payload through libmagic.
564 * If the attempt is successful, the guessed content type will automatically
565 * be set as response content type.
566 *
567 * Expects a string parameter specifying the magic.mime database to use.
568 * Additionally accepts an optional int parameter, being flags for libmagic.
569 *
570 * Returns the guessed content type on success, or FALSE on failure.
571 *
572 * Throws HttpRuntimeException, HttpInvalidParamException
573 * if http.only_exceptions is TRUE.
574 */
575 PHP_METHOD(HttpResponse, guessContentType)
576 {
577 char *magic_file, *ct = NULL;
578 int magic_file_len;
579 long magic_mode = 0;
580
581 RETVAL_FALSE;
582
583 #ifdef HTTP_HAVE_MAGIC
584 magic_mode = MAGIC_MIME;
585
586 SET_EH_THROW_HTTP();
587 if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &magic_file, &magic_file_len, &magic_mode)) {
588 switch (Z_LVAL_P(GET_STATIC_PROP(mode))) {
589 case SEND_DATA:
590 {
591 zval *data = GET_STATIC_PROP(data);
592 ct = http_guess_content_type(magic_file, magic_mode, Z_STRVAL_P(data), Z_STRLEN_P(data), SEND_DATA);
593 }
594 break;
595
596 case SEND_RSRC:
597 {
598 php_stream *s;
599 zval *z = GET_STATIC_PROP(stream);
600 z->type = IS_RESOURCE;
601 php_stream_from_zval(s, &z);
602 ct = http_guess_content_type(magic_file, magic_mode, s, 0, SEND_RSRC);
603 }
604 break;
605
606 default:
607 ct = http_guess_content_type(magic_file, magic_mode, Z_STRVAL_P(GET_STATIC_PROP(file)), 0, -1);
608 break;
609 }
610 if (ct) {
611 UPD_STATIC_PROP(string, contentType, ct);
612 RETVAL_STRING(ct, 0);
613 }
614 }
615 SET_EH_NORMAL();
616 #else
617 http_error(HE_THROW, HTTP_E_RUNTIME, "Cannot guess Content-Type; libmagic not available");
618 #endif
619 }
620 /* }}} */
621
622 /* {{{ proto static bool HttpResponse::setContentDisposition(string filename[, bool inline = false])
623 *
624 * Set the Content-Disposition. The Content-Disposition header is very useful
625 * if the data actually sent came from a file or something similar, that should
626 * be "saved" by the client/user (i.e. by browsers "Save as..." popup window).
627 *
628 * Expects a string parameter specifying the file name the "Save as..." dialogue
629 * should display. Optionally accepts a bool parameter, which, if set to true
630 * and the user agent knows how to handle the content type, will probably not
631 * cause the popup window to be shown.
632 *
633 * Returns TRUE on success or FALSE on failure.
634 */
635 PHP_METHOD(HttpResponse, setContentDisposition)
636 {
637 char *file, *cd;
638 int file_len;
639 size_t cd_len;
640 zend_bool send_inline = 0;
641
642 if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|b", &file, &file_len, &send_inline)) {
643 RETURN_FALSE;
644 }
645
646 cd_len = spprintf(&cd, 0, "%s; filename=\"%s\"", send_inline ? "inline" : "attachment", file);
647 RETVAL_SUCCESS(UPD_STATIC_STRL(contentDisposition, cd, cd_len));
648 efree(cd);
649 }
650 /* }}} */
651
652 /* {{{ proto static string HttpResponse::getContentDisposition()
653 *
654 * Get current Content-Disposition setting.
655 *
656 * Returns the current content disposition as string like sent in a header.
657 */
658 PHP_METHOD(HttpResponse, getContentDisposition)
659 {
660 NO_ARGS;
661
662 IF_RETVAL_USED {
663 zval *cd_p, *cd = convert_to_type_ex(IS_STRING, GET_STATIC_PROP(contentDisposition), &cd_p);
664
665 RETVAL_ZVAL(cd, 1, 0);
666
667 if (cd_p) {
668 zval_ptr_dtor(&cd_p);
669 }
670 }
671 }
672 /* }}} */
673
674 /* {{{ proto static bool HttpResponse::setETag(string etag)
675 *
676 * Set a custom ETag. Use this only if you know what you're doing.
677 *
678 * Expects an unquoted string as parameter containing the ETag.
679 *
680 * Returns TRUE on success, or FALSE on failure.
681 */
682 PHP_METHOD(HttpResponse, setETag)
683 {
684 char *etag;
685 int etag_len;
686
687 if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &etag, &etag_len)) {
688 RETURN_FALSE;
689 }
690
691 RETURN_SUCCESS(UPD_STATIC_STRL(eTag, etag, etag_len));
692 }
693 /* }}} */
694
695 /* {{{ proto static string HttpResponse::getETag()
696 *
697 * Get calculated or previously set custom ETag.
698 *
699 * Returns the calculated or previously set ETag as unquoted string.
700 */
701 PHP_METHOD(HttpResponse, getETag)
702 {
703 NO_ARGS;
704
705 IF_RETVAL_USED {
706 zval *etag_p, *etag = convert_to_type_ex(IS_STRING, GET_STATIC_PROP(eTag), &etag_p);
707
708 RETVAL_ZVAL(etag, 1, 0);
709
710 if (etag_p) {
711 zval_ptr_dtor(&etag_p);
712 }
713 }
714 }
715 /* }}} */
716
717 /* {{{ proto static bool HttpResponse::setLastModified(int timestamp)
718 *
719 * Set a custom Last-Modified date.
720 *
721 * Expects an unix timestamp as parameter representing the last modification
722 * time of the sent entity.
723 *
724 * Returns TRUE on success, or FALSE on failure.
725 */
726 PHP_METHOD(HttpResponse, setLastModified)
727 {
728 long lm;
729
730 if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &lm)) {
731 RETURN_FALSE;
732 }
733
734 RETURN_SUCCESS(UPD_STATIC_PROP(long, lastModified, lm));
735 }
736 /* }}} */
737
738 /* {{{ proto static int HttpResponse::getLastModified()
739 *
740 * Get calculated or previously set custom Last-Modified date.
741 *
742 * Returns the calculated or previously set unix timestamp.
743 */
744 PHP_METHOD(HttpResponse, getLastModified)
745 {
746 NO_ARGS;
747
748 IF_RETVAL_USED {
749 zval *lm_p, *lm = convert_to_type_ex(IS_LONG, GET_STATIC_PROP(lastModified), &lm_p);
750
751 RETVAL_ZVAL(lm, 1, 0);
752
753 if (lm_p) {
754 zval_ptr_dtor(&lm_p);
755 }
756 }
757 }
758 /* }}} */
759
760 /* {{{ proto static bool HttpResponse::setThrottleDelay(double seconds)
761 *
762 * Sets the throttle delay for use with HttpResponse::setBufferSize().
763 *
764 * Provides a basic throttling mechanism, which will yield the current process
765 * resp. thread until the entity has been completely sent, though.
766 *
767 * Note: This doesn't really work with the FastCGI SAPI.
768 *
769 * Expects a double parameter specifying the seconds too sleep() after
770 * each chunk sent.
771 *
772 * Returns TRUE on success, or FALSE on failure.
773 */
774 PHP_METHOD(HttpResponse, setThrottleDelay)
775 {
776 double seconds;
777
778 if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "d", &seconds)) {
779 RETURN_FALSE;
780 }
781 RETURN_SUCCESS(UPD_STATIC_PROP(double, throttleDelay, seconds));
782 }
783 /* }}} */
784
785 /* {{{ proto static double HttpResponse::getThrottleDelay()
786 *
787 * Get the current throttle delay.
788 *
789 * Returns a double representing the throttle delay in seconds.
790 */
791 PHP_METHOD(HttpResponse, getThrottleDelay)
792 {
793 NO_ARGS;
794
795 IF_RETVAL_USED {
796 zval *delay_p, *delay = convert_to_type_ex(IS_DOUBLE, GET_STATIC_PROP(throttleDelay), &delay_p);
797
798 RETVAL_ZVAL(delay, 1, 0);
799
800 if (delay_p) {
801 zval_ptr_dtor(&delay_p);
802 }
803 }
804 }
805 /* }}} */
806
807 /* {{{ proto static bool HttpResponse::setBufferSize(int bytes)
808 *
809 * Sets the send buffer size for use with HttpResponse::setThrottleDelay().
810 *
811 * Provides a basic throttling mechanism, which will yield the current process
812 * resp. thread until the entity has been completely sent, though.
813 *
814 * Note: This doesn't really work with the FastCGI SAPI.
815 *
816 * Expects an int parameter representing the chunk size in bytes.
817 *
818 * Returns TRUE on success, or FALSE on failure.
819 */
820 PHP_METHOD(HttpResponse, setBufferSize)
821 {
822 long bytes;
823
824 if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &bytes)) {
825 RETURN_FALSE;
826 }
827 RETURN_SUCCESS(UPD_STATIC_PROP(long, bufferSize, bytes));
828 }
829 /* }}} */
830
831 /* {{{ proto static int HttpResponse::getBufferSize()
832 *
833 * Get current buffer size.
834 *
835 * Returns an int representing the current buffer size in bytes.
836 */
837 PHP_METHOD(HttpResponse, getBufferSize)
838 {
839 NO_ARGS;
840
841 IF_RETVAL_USED {
842 zval *size_p, *size = convert_to_type_ex(IS_LONG, GET_STATIC_PROP(bufferSize), &size_p);
843
844 RETVAL_ZVAL(size, 1, 0);
845
846 if (size_p) {
847 zval_ptr_dtor(&size_p);
848 }
849 }
850 }
851 /* }}} */
852
853 /* {{{ proto static bool HttpResponse::setData(mixed data)
854 *
855 * Set the data to be sent.
856 *
857 * Expects one parameter, which will be converted to a string and contains
858 * the data to send.
859 *
860 * Returns TRUE on success, or FALSE on failure.
861 */
862 PHP_METHOD(HttpResponse, setData)
863 {
864 char *etag;
865 zval *the_data;
866
867 if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &the_data)) {
868 RETURN_FALSE;
869 }
870 if (Z_TYPE_P(the_data) != IS_STRING) {
871 convert_to_string_ex(&the_data);
872 }
873
874 if ( (SUCCESS != SET_STATIC_PROP(data, the_data)) ||
875 (SUCCESS != UPD_STATIC_PROP(long, mode, SEND_DATA))) {
876 RETURN_FALSE;
877 }
878
879 UPD_STATIC_PROP(long, lastModified, http_last_modified(the_data, SEND_DATA));
880 if ((etag = http_etag(Z_STRVAL_P(the_data), Z_STRLEN_P(the_data), SEND_DATA))) {
881 UPD_STATIC_PROP(string, eTag, etag);
882 efree(etag);
883 }
884
885 RETURN_TRUE;
886 }
887 /* }}} */
888
889 /* {{{ proto static string HttpResponse::getData()
890 *
891 * Get the previously set data to be sent.
892 *
893 * Returns a string containing the previously set data to send.
894 */
895 PHP_METHOD(HttpResponse, getData)
896 {
897 NO_ARGS;
898
899 IF_RETVAL_USED {
900 zval *the_data = GET_STATIC_PROP(data);
901
902 RETURN_ZVAL(the_data, 1, 0);
903 }
904 }
905 /* }}} */
906
907 /* {{{ proto static bool HttpResponse::setStream(resource stream)
908 *
909 * Set the resource to be sent.
910 *
911 * Expects a resource parameter referencing an already opened stream from
912 * which the data to send will be read.
913 *
914 * Returns TRUE on success, or FALSE on failure.
915 */
916 PHP_METHOD(HttpResponse, setStream)
917 {
918 char *etag;
919 zval *the_stream;
920 php_stream *the_real_stream;
921 php_stream_statbuf ssb;
922
923 if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &the_stream)) {
924 RETURN_FALSE;
925 }
926
927 php_stream_from_zval(the_real_stream, &the_stream);
928 if (php_stream_stat(the_real_stream, &ssb)) {
929 RETURN_FALSE;
930 }
931
932 if ( (SUCCESS != UPD_STATIC_PROP(long, stream, Z_LVAL_P(the_stream))) ||
933 (SUCCESS != UPD_STATIC_PROP(long, mode, SEND_RSRC))) {
934 RETURN_FALSE;
935 }
936 zend_list_addref(Z_LVAL_P(the_stream));
937
938 UPD_STATIC_PROP(long, lastModified, http_last_modified(the_real_stream, SEND_RSRC));
939 if ((etag = http_etag(the_real_stream, 0, SEND_RSRC))) {
940 UPD_STATIC_PROP(string, eTag, etag);
941 efree(etag);
942 }
943
944 RETURN_TRUE;
945 }
946 /* }}} */
947
948 /* {{{ proto static resource HttpResponse::getStream()
949 *
950 * Get the previously set resource to be sent.
951 *
952 * Returns the previously set resource.
953 */
954 PHP_METHOD(HttpResponse, getStream)
955 {
956 NO_ARGS;
957
958 IF_RETVAL_USED {
959 zval *stream_p;
960
961 RETVAL_RESOURCE(Z_LVAL_P(convert_to_type_ex(IS_LONG, GET_STATIC_PROP(stream), &stream_p)));
962
963 if (stream_p) {
964 zval_ptr_dtor(&stream_p);
965 }
966 }
967 }
968 /* }}} */
969
970 /* {{{ proto static bool HttpResponse::setFile(string file)
971 *
972 * Set the file to be sent.
973 *
974 * Expects a string as parameter, specifying the path to the file to send.
975 *
976 * Returns TRUE on success, or FALSE on failure.
977 */
978 PHP_METHOD(HttpResponse, setFile)
979 {
980 char *the_file, *etag;
981 int file_len;
982 php_stream_statbuf ssb;
983
984 if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &the_file, &file_len)) {
985 RETURN_FALSE;
986 }
987
988 if (php_stream_stat_path(the_file, &ssb)) {
989 RETURN_FALSE;
990 }
991
992 if ( (SUCCESS != UPD_STATIC_STRL(file, the_file, file_len)) ||
993 (SUCCESS != UPD_STATIC_PROP(long, mode, -1))) {
994 RETURN_FALSE;
995 }
996
997 UPD_STATIC_PROP(long, lastModified, http_last_modified(the_file, -1));
998 if ((etag = http_etag(the_file, 0, -1))) {
999 UPD_STATIC_PROP(string, eTag, etag);
1000 efree(etag);
1001 }
1002
1003 RETURN_TRUE;
1004 }
1005 /* }}} */
1006
1007 /* {{{ proto static string HttpResponse::getFile()
1008 *
1009 * Get the previously set file to be sent.
1010 *
1011 * Returns the previously set path to the file to send as string.
1012 */
1013 PHP_METHOD(HttpResponse, getFile)
1014 {
1015 NO_ARGS;
1016
1017 IF_RETVAL_USED {
1018 zval *the_file_p, *the_file = convert_to_type_ex(IS_STRING, GET_STATIC_PROP(file), &the_file_p);
1019
1020 RETVAL_ZVAL(the_file, 1, 0);
1021
1022 if (the_file_p) {
1023 zval_ptr_dtor(&the_file_p);
1024 }
1025 }
1026 }
1027 /* }}} */
1028
1029 /* {{{ proto static bool HttpResponse::send([bool clean_ob = true])
1030 *
1031 * Finally send the entity.
1032 *
1033 * Accepts an optional boolean parameter, specifying wheter the ouput
1034 * buffers should be discarded prior sending. A successful caching attempt
1035 * will cause a script termination, and write a log entry if the INI setting
1036 * http.cache_log is set.
1037 *
1038 * Returns TRUE on success, or FALSE on failure.
1039 *
1040 * Throws HttpHeaderException, HttpResponseException if http.onyl_excpetions is TRUE.
1041 *
1042 * Example:
1043 * <pre>
1044 * <?php
1045 * HttpResponse::setCache(true);
1046 * HttpResponse::setContentType('application/pdf');
1047 * HttpResponse::setContentDisposition("$user.pdf", false);
1048 * HttpResponse::setFile('sheet.pdf');
1049 * HttpResponse::send();
1050 * ?>
1051 * </pre>
1052 */
1053 PHP_METHOD(HttpResponse, send)
1054 {
1055 zval *sent;
1056 zend_bool clean_ob = 1;
1057
1058 if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|b", &clean_ob)) {
1059 RETURN_FALSE;
1060 }
1061
1062 HTTP_CHECK_HEADERS_SENT(RETURN_FALSE);
1063
1064 sent = GET_STATIC_PROP(sent);
1065 if (Z_LVAL_P(sent)) {
1066 http_error(HE_WARNING, HTTP_E_RESPONSE, "Cannot send HttpResponse, response has already been sent");
1067 RETURN_FALSE;
1068 } else {
1069 Z_LVAL_P(sent) = 1;
1070 }
1071
1072 /* capture mode */
1073 if (zval_is_true(GET_STATIC_PROP(catch))) {
1074 zval *etag_p, *the_data;
1075
1076 MAKE_STD_ZVAL(the_data);
1077 php_ob_get_buffer(the_data TSRMLS_CC);
1078 SET_STATIC_PROP(data, the_data);
1079 ZVAL_LONG(GET_STATIC_PROP(mode), SEND_DATA);
1080
1081 if (!Z_STRLEN_P(convert_to_type_ex(IS_STRING, GET_STATIC_PROP(eTag), &etag_p))) {
1082 char *etag = http_etag(Z_STRVAL_P(the_data), Z_STRLEN_P(the_data), SEND_DATA);
1083 if (etag) {
1084 UPD_STATIC_PROP(string, eTag, etag);
1085 efree(etag);
1086 }
1087 }
1088 zval_ptr_dtor(&the_data);
1089
1090 if (etag_p) {
1091 zval_ptr_dtor(&etag_p);
1092 }
1093
1094 clean_ob = 1;
1095 }
1096
1097 if (clean_ob) {
1098 /* interrupt on-the-fly etag generation */
1099 HTTP_G(etag).started = 0;
1100 /* discard previous output buffers */
1101 php_end_ob_buffers(0 TSRMLS_CC);
1102 }
1103
1104 /* caching */
1105 if (zval_is_true(GET_STATIC_PROP(cache))) {
1106 zval *cctl, *cctl_p, *etag, *etag_p, *lmod, *lmod_p;
1107
1108 etag = convert_to_type_ex(IS_STRING, GET_STATIC_PROP(eTag), &etag_p);
1109 lmod = convert_to_type_ex(IS_LONG, GET_STATIC_PROP(lastModified), &lmod_p);
1110 cctl = convert_to_type_ex(IS_STRING, GET_STATIC_PROP(cacheControl), &cctl_p);
1111
1112 http_cache_etag(Z_STRVAL_P(etag), Z_STRLEN_P(etag), Z_STRVAL_P(cctl), Z_STRLEN_P(cctl));
1113 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));
1114
1115 if (etag_p) zval_ptr_dtor(&etag_p);
1116 if (lmod_p) zval_ptr_dtor(&lmod_p);
1117 if (cctl_p) zval_ptr_dtor(&cctl_p);
1118
1119 if (php_ob_handler_used("blackhole" TSRMLS_CC)) {
1120 RETURN_TRUE;
1121 }
1122 }
1123
1124 /* content type */
1125 {
1126 zval *ctype_p, *ctype = convert_to_type_ex(IS_STRING, GET_STATIC_PROP(contentType), &ctype_p);
1127 if (Z_STRLEN_P(ctype)) {
1128 http_send_content_type(Z_STRVAL_P(ctype), Z_STRLEN_P(ctype));
1129 } else {
1130 char *ctypes = INI_STR("default_mimetype");
1131 size_t ctlen = ctypes ? strlen(ctypes) : 0;
1132
1133 if (ctlen) {
1134 http_send_content_type(ctypes, ctlen);
1135 } else {
1136 http_send_content_type("application/x-octetstream", lenof("application/x-octetstream"));
1137 }
1138 }
1139 if (ctype_p) {
1140 zval_ptr_dtor(&ctype_p);
1141 }
1142 }
1143
1144 /* content disposition */
1145 {
1146 zval *cd_p, *cd = convert_to_type_ex(IS_STRING, GET_STATIC_PROP(contentDisposition), &cd_p);
1147 if (Z_STRLEN_P(cd)) {
1148 http_send_header_ex("Content-Disposition", lenof("Content-Disposition"), Z_STRVAL_P(cd), Z_STRLEN_P(cd), 1, NULL);
1149 }
1150 if (cd_p) {
1151 zval_ptr_dtor(&cd_p);
1152 }
1153 }
1154
1155 /* throttling */
1156 {
1157 zval *bsize_p, *bsize = convert_to_type_ex(IS_LONG, GET_STATIC_PROP(bufferSize), &bsize_p);
1158 zval *delay_p, *delay = convert_to_type_ex(IS_DOUBLE, GET_STATIC_PROP(throttleDelay), &delay_p);
1159 HTTP_G(send).buffer_size = Z_LVAL_P(bsize);
1160 HTTP_G(send).throttle_delay = Z_DVAL_P(delay);
1161 if (bsize_p) zval_ptr_dtor(&bsize_p);
1162 if (delay_p) zval_ptr_dtor(&delay_p);
1163 }
1164
1165 /* gzip */
1166 HTTP_G(send).gzip_encoding = zval_is_true(GET_STATIC_PROP(gzip));
1167
1168 /* start ob */
1169 php_start_ob_buffer(NULL, HTTP_G(send).buffer_size, 0 TSRMLS_CC);
1170
1171 /* send */
1172 switch (Z_LVAL_P(GET_STATIC_PROP(mode)))
1173 {
1174 case SEND_DATA:
1175 {
1176 zval *zdata_p, *zdata = convert_to_type_ex(IS_STRING, GET_STATIC_PROP(data), &zdata_p);
1177 RETVAL_SUCCESS(http_send_data_ex(Z_STRVAL_P(zdata), Z_STRLEN_P(zdata), 1));
1178 if (zdata_p) zval_ptr_dtor(&zdata_p);
1179 return;
1180 }
1181
1182 case SEND_RSRC:
1183 {
1184 php_stream *the_real_stream;
1185 zval *the_stream_p, *the_stream = convert_to_type_ex(IS_LONG, GET_STATIC_PROP(stream), &the_stream_p);
1186 the_stream->type = IS_RESOURCE;
1187 php_stream_from_zval(the_real_stream, &the_stream);
1188 RETVAL_SUCCESS(http_send_stream_ex(the_real_stream, 0, 1));
1189 if (the_stream_p) zval_ptr_dtor(&the_stream_p);
1190 return;
1191 }
1192
1193 default:
1194 {
1195 zval *file_p;
1196 RETVAL_SUCCESS(http_send_file_ex(Z_STRVAL_P(convert_to_type_ex(IS_STRING, GET_STATIC_PROP(file), &file_p)), 1));
1197 if (file_p) zval_ptr_dtor(&file_p);
1198 return;
1199 }
1200 }
1201 }
1202 /* }}} */
1203
1204 /* {{{ proto static void HttpResponse::capture()
1205 *
1206 * Capture script output.
1207 *
1208 * Example:
1209 * <pre>
1210 * <?php
1211 * HttpResponse::setCache(true);
1212 * HttpResponse::capture();
1213 * // script follows
1214 * ?>
1215 * </pre>
1216 */
1217 PHP_METHOD(HttpResponse, capture)
1218 {
1219 NO_ARGS;
1220
1221 HTTP_CHECK_HEADERS_SENT(RETURN_FALSE);
1222
1223 UPD_STATIC_PROP(long, catch, 1);
1224
1225 php_end_ob_buffers(0 TSRMLS_CC);
1226 php_start_ob_buffer(NULL, 40960, 0 TSRMLS_CC);
1227
1228 /* register shutdown function */
1229 {
1230 zval func, retval, arg, *argp[1];
1231
1232 INIT_PZVAL(&arg);
1233 INIT_PZVAL(&func);
1234 INIT_PZVAL(&retval);
1235 ZVAL_STRINGL(&func, "register_shutdown_function", lenof("register_shutdown_function"), 0);
1236
1237 array_init(&arg);
1238 add_next_index_stringl(&arg, "HttpResponse", lenof("HttpResponse"), 1);
1239 add_next_index_stringl(&arg, "send", lenof("send"), 1);
1240 argp[0] = &arg;
1241 call_user_function(EG(function_table), NULL, &func, &retval, 1, argp TSRMLS_CC);
1242 zval_dtor(&arg);
1243 }
1244 }
1245 /* }}} */
1246
1247 #endif /* ZEND_ENGINE_2 && !WONKY */
1248
1249 /*
1250 * Local variables:
1251 * tab-width: 4
1252 * c-basic-offset: 4
1253 * End:
1254 * vim600: noet sw=4 ts=4 fdm=marker
1255 * vim<600: noet sw=4 ts=4
1256 */
1257