cache only standard GET/HEAD by default
[m6w6/ext-http] / php_http_env_response.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-2011, Michael Wallner <mike@php.net> |
10 +--------------------------------------------------------------------+
11 */
12
13 #include "php_http_api.h"
14
15 static void set_option(zval *options, const char *name_str, size_t name_len, int type, const void *value_ptr, size_t value_len TSRMLS_DC)
16 {
17 if (Z_TYPE_P(options) == IS_OBJECT) {
18 /* stupid non-const api */
19 char *name = estrndup(name_str, name_len);
20 if (value_ptr) {
21 switch (type) {
22 case IS_DOUBLE:
23 zend_update_property_double(Z_OBJCE_P(options), options, name, name_len, *(double *)value_ptr TSRMLS_CC);
24 break;
25 case IS_LONG:
26 zend_update_property_long(Z_OBJCE_P(options), options, name, name_len, *(long *)value_ptr TSRMLS_CC);
27 break;
28 case IS_STRING:
29 zend_update_property_stringl(Z_OBJCE_P(options), options, name, name_len, value_ptr, value_len TSRMLS_CC);
30 break;
31 }
32 } else {
33 zend_update_property_null(Z_OBJCE_P(options), options, name, name_len TSRMLS_CC);
34 }
35 efree(name);
36 } else {
37 convert_to_array(options);
38 if (value_ptr) {
39 switch (type) {
40 case IS_DOUBLE:
41 add_assoc_double_ex(options, name_str, name_len + 1, *(double *)value_ptr);
42 break;
43 case IS_LONG:
44 add_assoc_long_ex(options, name_str, name_len + 1, *(long *)value_ptr);
45 break;
46 case IS_STRING: {
47 char *value = estrndup(value_ptr, value_len);
48 add_assoc_stringl_ex(options, name_str, name_len + 1, value, value_len, 0);
49 break;
50 }
51 }
52 } else {
53 add_assoc_null_ex(options, name_str, name_len + 1);
54 }
55 }
56 }
57 static zval *get_option(zval *options, const char *name_str, size_t name_len TSRMLS_DC)
58 {
59 zval *val, **valptr;
60
61 if (Z_TYPE_P(options) == IS_OBJECT) {
62 char *name = estrndup(name_str, name_len);
63 val = zend_read_property(Z_OBJCE_P(options), options, name, name_len, 0 TSRMLS_CC);
64 efree(name);
65 } else {
66 if (SUCCESS == zend_symtable_find(Z_ARRVAL_P(options), name_str, name_len + 1, (void *) &valptr)) {
67 val = *valptr;
68 } else {
69 val = NULL;
70 }
71 }
72 if (val) {
73 Z_ADDREF_P(val);
74 }
75 return val;
76 }
77
78 PHP_HTTP_API php_http_cache_status_t php_http_env_is_response_cached_by_etag(zval *options, const char *header_str, size_t header_len TSRMLS_DC)
79 {
80 int ret = 0, free_etag = 0;
81 char *header = NULL, *etag;
82 zval *zetag, *zbody = NULL;
83
84 if ( !(zbody = get_option(options, ZEND_STRL("body") TSRMLS_CC))
85 || !(Z_TYPE_P(zbody) == IS_OBJECT)
86 || !instanceof_function(Z_OBJCE_P(zbody), php_http_message_body_class_entry TSRMLS_CC)
87 ) {
88 if (zbody) {
89 zval_ptr_dtor(&zbody);
90 }
91 return PHP_HTTP_CACHE_NO;
92 }
93
94 if ((zetag = get_option(options, ZEND_STRL("etag") TSRMLS_CC))) {
95 zval *zetag_copy = php_http_ztyp(IS_STRING, zetag);
96 zval_ptr_dtor(&zetag);
97 zetag = zetag_copy;
98 }
99
100 if (zetag && Z_STRLEN_P(zetag)) {
101 etag = Z_STRVAL_P(zetag);
102 } else if ((etag = php_http_message_body_etag(((php_http_message_body_object_t *) zend_object_store_get_object(zbody TSRMLS_CC))->body))) {
103 set_option(options, ZEND_STRL("etag"), IS_STRING, etag, strlen(etag) TSRMLS_CC);
104 free_etag = 1;
105 }
106
107 if (zbody) {
108 zval_ptr_dtor(&zbody);
109 }
110
111 if (zetag) {
112 zval_ptr_dtor(&zetag);
113 }
114
115 if (etag && (header = php_http_env_get_request_header(header_str, header_len, NULL TSRMLS_CC))) {
116 ret = php_http_match(header, etag, PHP_HTTP_MATCH_WORD);
117 }
118
119 if (free_etag) {
120 efree(etag);
121 }
122 STR_FREE(header);
123
124 return ret ? PHP_HTTP_CACHE_HIT : PHP_HTTP_CACHE_MISS;
125 }
126
127 PHP_HTTP_API php_http_cache_status_t php_http_env_is_response_cached_by_last_modified(zval *options, const char *header_str, size_t header_len TSRMLS_DC)
128 {
129 char *header;
130 time_t ums, lm = 0;
131 zval *zbody = NULL, *zlm;
132
133 if ( !(header = php_http_env_get_request_header(header_str, header_len, NULL TSRMLS_CC))
134 || !(zbody = get_option(options, ZEND_STRL("body") TSRMLS_CC))
135 || !(Z_TYPE_P(zbody) == IS_OBJECT)
136 || !instanceof_function(Z_OBJCE_P(zbody), php_http_message_body_class_entry TSRMLS_CC)
137 ) {
138 STR_FREE(header);
139 if (zbody) {
140 zval_ptr_dtor(&zbody);
141 }
142 return PHP_HTTP_CACHE_NO;
143 }
144
145 if ((zlm = get_option(options, ZEND_STRL("lastModified") TSRMLS_CC))) {
146 zval *zlm_copy = php_http_ztyp(IS_LONG, zlm);
147 zval_ptr_dtor(&zlm);
148 zlm = zlm_copy;
149 }
150
151 if (zlm && Z_LVAL_P(zlm) > 0) {
152 lm = Z_LVAL_P(zlm);
153 } else {
154 lm = php_http_message_body_mtime(((php_http_message_body_object_t *) zend_object_store_get_object(zbody TSRMLS_CC))->body);
155 set_option(options, ZEND_STRL("lastModified"), IS_LONG, &lm, 0 TSRMLS_CC);
156 }
157
158 if (zlm) {
159 zval_ptr_dtor(&zlm);
160 }
161
162 ums = php_parse_date(header, NULL);
163 efree(header);
164
165 if (ums > 0 && ums <= lm) {
166 return PHP_HTTP_CACHE_HIT;
167 } else {
168 return PHP_HTTP_CACHE_MISS;
169 }
170 }
171
172 static size_t output(void *context, const char *buf, size_t len TSRMLS_DC)
173 {
174 php_http_env_response_t *r = context;
175
176 PHPWRITE(buf, len);
177
178 /* we really only need to flush when throttling is enabled,
179 because we push the data as fast as possible anyway if not */
180 if (r->throttle.delay >= PHP_HTTP_DIFFSEC) {
181 if (php_output_get_level(TSRMLS_C)) {
182 php_output_flush_all(TSRMLS_C);
183 }
184 if (!(php_output_get_status(TSRMLS_C) & PHP_OUTPUT_IMPLICITFLUSH)) {
185 sapi_flush(TSRMLS_C);
186 }
187 php_http_sleep(r->throttle.delay);
188 }
189 return len;
190 }
191
192 #define php_http_env_response_send_done(r) php_http_env_response_send_data((r), NULL, 0)
193 static STATUS php_http_env_response_send_data(php_http_env_response_t *r, const char *buf, size_t len)
194 {
195 TSRMLS_FETCH_FROM_CTX(r->ts);
196 size_t chunk = r->throttle.chunk ? r->throttle.chunk : PHP_HTTP_SENDBUF_SIZE;
197
198 if (r->content.encoder) {
199 char *enc_str = NULL;
200 size_t enc_len = 0;
201
202 if (buf) {
203 if (SUCCESS != php_http_encoding_stream_update(r->content.encoder, buf, len, &enc_str, &enc_len)) {
204 return FAILURE;
205 }
206 } else {
207 if (SUCCESS != php_http_encoding_stream_finish(r->content.encoder, &enc_str, &enc_len)) {
208 return FAILURE;
209 }
210 }
211
212 if (enc_str) {
213 php_http_buffer_chunked_output(&r->buffer, enc_str, enc_len, buf ? chunk : 0, output, r TSRMLS_CC);
214 STR_FREE(enc_str);
215 }
216 } else {
217 php_http_buffer_chunked_output(&r->buffer, buf, len, buf ? chunk : 0, output, r TSRMLS_CC);
218 }
219
220 return SUCCESS;
221 }
222
223 PHP_HTTP_API php_http_env_response_t *php_http_env_response_init(php_http_env_response_t *r, zval *options TSRMLS_DC)
224 {
225 php_http_env_response_t *free_r = NULL;
226
227 if (!r) {
228 r = free_r = emalloc(sizeof(*r));
229 }
230 memset(r, 0, sizeof(*r));
231
232 r->buffer = php_http_buffer_init(NULL);
233
234 Z_ADDREF_P(options);
235 r->options = options;
236
237 TSRMLS_SET_CTX(r->ts);
238
239 return r;
240 }
241
242 PHP_HTTP_API void php_http_env_response_dtor(php_http_env_response_t *r)
243 {
244 php_http_buffer_free(&r->buffer);
245 zval_ptr_dtor(&r->options);
246 STR_FREE(r->content.type);
247 STR_FREE(r->content.encoding);
248 if (r->content.encoder) {
249 php_http_encoding_stream_free(&r->content.encoder);
250 }
251 }
252
253 PHP_HTTP_API void php_http_env_response_free(php_http_env_response_t **r)
254 {
255 if (*r) {
256 php_http_env_response_dtor(*r);
257 efree(*r);
258 *r = NULL;
259 }
260 }
261
262 static STATUS php_http_env_response_send_head(php_http_env_response_t *r)
263 {
264 STATUS ret = SUCCESS;
265 zval *zoption, *options = r->options;
266 TSRMLS_FETCH_FROM_CTX(r->ts);
267
268 if (r->done) {
269 return ret;
270 }
271
272 if ((zoption = get_option(options, ZEND_STRL("responseCode") TSRMLS_CC))) {
273 zval *zoption_copy = php_http_ztyp(IS_LONG, zoption);
274
275 zval_ptr_dtor(&zoption);
276 if (Z_LVAL_P(zoption_copy) > 0) {
277 ret = php_http_env_set_response_code(Z_LVAL_P(zoption_copy) TSRMLS_CC);
278 }
279 zval_ptr_dtor(&zoption_copy);
280 }
281
282 if (ret != SUCCESS) {
283 return ret;
284 }
285
286 if ((zoption = get_option(options, ZEND_STRL("httpVersion") TSRMLS_CC))) {
287 php_http_version_t v;
288 zval *zoption_copy = php_http_ztyp(IS_STRING, zoption);
289
290 zval_ptr_dtor(&zoption);
291 if (Z_STRLEN_P(zoption_copy) && php_http_version_parse(&v, Z_STRVAL_P(zoption_copy) TSRMLS_CC)) {
292 ret = php_http_env_set_response_protocol_version(&v TSRMLS_CC);
293 php_http_version_dtor(&v);
294 }
295 zval_ptr_dtor(&zoption_copy);
296 }
297
298 if (ret != SUCCESS) {
299 return ret;
300 }
301
302 if ((zoption = get_option(options, ZEND_STRL("headers") TSRMLS_CC))) {
303 if (Z_TYPE_P(zoption) == IS_ARRAY) {
304 zval **val;
305 HashPosition pos;
306 php_http_array_hashkey_t key = php_http_array_hashkey_init(0);
307
308 FOREACH_KEYVAL(pos, zoption, key, val) {
309 if (key.type == HASH_KEY_IS_STRING) {
310 if (SUCCESS != (ret = php_http_env_set_response_header_value(0, key.str, key.len - 1, *val, 1 TSRMLS_CC))) {
311 break;
312 }
313 }
314 }
315 }
316 zval_ptr_dtor(&zoption);
317 }
318
319 if (ret != SUCCESS) {
320 return ret;
321 }
322
323 if ((zoption = get_option(options, ZEND_STRL("contentType") TSRMLS_CC))) {
324 zval *zoption_copy = php_http_ztyp(IS_STRING, zoption);
325
326 zval_ptr_dtor(&zoption);
327 if (Z_STRLEN_P(zoption_copy)) {
328 PHP_HTTP_CHECK_CONTENT_TYPE(Z_STRVAL_P(zoption_copy), ret = FAILURE) else {
329 if (SUCCESS == (ret = php_http_env_set_response_header_format(0, 1 TSRMLS_CC, "Content-Type: %.*s", Z_STRLEN_P(zoption_copy), Z_STRVAL_P(zoption_copy)))) {
330 r->content.type = estrndup(Z_STRVAL_P(zoption_copy), Z_STRLEN_P(zoption_copy));
331 }
332 }
333 }
334 zval_ptr_dtor(&zoption_copy);
335 }
336
337 if (ret != SUCCESS) {
338 return ret;
339 }
340
341 if (r->range.status == PHP_HTTP_RANGE_OK) {
342 if (zend_hash_num_elements(&r->range.values) == 1) {
343 zval **range, **begin, **end;
344
345 if (SUCCESS != zend_hash_index_find(&r->range.values, 0, (void *) &range)
346 || SUCCESS != zend_hash_index_find(Z_ARRVAL_PP(range), 0, (void *) &begin)
347 || SUCCESS != zend_hash_index_find(Z_ARRVAL_PP(range), 1, (void *) &end)
348 ) {
349 /* this should never happen */
350 zend_hash_destroy(&r->range.values);
351 php_http_env_set_response_code(500 TSRMLS_CC);
352 ret = FAILURE;
353 } else {
354 ret = php_http_env_set_response_header_format(206, 1 TSRMLS_CC, "Content-Range: bytes %ld-%ld/%zu", Z_LVAL_PP(begin), Z_LVAL_PP(end), r->content.length);
355 }
356 } else {
357 php_http_boundary(r->range.boundary, sizeof(r->range.boundary) TSRMLS_CC);
358 ret = php_http_env_set_response_header_format(206, 1 TSRMLS_CC, "Content-Type: multipart/byteranges; boundary=%s", r->range.boundary);
359 }
360 } else {
361 if ((zoption = get_option(options, ZEND_STRL("cacheControl") TSRMLS_CC))) {
362 zval *zoption_copy = php_http_ztyp(IS_STRING, zoption);
363
364 zval_ptr_dtor(&zoption);
365 if (Z_STRLEN_P(zoption_copy)) {
366 ret = php_http_env_set_response_header_format(0, 1 TSRMLS_CC, "Cache-Control: %.*s", Z_STRLEN_P(zoption_copy), Z_STRVAL_P(zoption_copy) TSRMLS_CC);
367 }
368 zval_ptr_dtor(&zoption_copy);
369 }
370
371 if (ret != SUCCESS) {
372 return ret;
373 }
374
375 if ((zoption = get_option(options, ZEND_STRL("contentDisposition") TSRMLS_CC))) {
376 zval *zoption_copy = php_http_ztyp(IS_ARRAY, zoption);
377 zval **zdisposition, **zfilename = NULL;
378
379 zval_ptr_dtor(&zoption);
380 if (SUCCESS == zend_hash_find(Z_ARRVAL_P(zoption_copy), ZEND_STRS("disposition"), (void *) &zdisposition)) {
381 zval *zdisposition_copy = php_http_ztyp(IS_LONG, *zdisposition);
382 char *tmp = NULL;
383
384 switch (Z_LVAL_P(zdisposition_copy)) {
385 case PHP_HTTP_CONTENT_DISPOSITION_NONE:
386 ret = php_http_env_set_response_header_value(0, ZEND_STRL("Content-Disposition"), NULL, 1 TSRMLS_CC);
387 break;
388 case PHP_HTTP_CONTENT_DISPOSITION_INLINE:
389 tmp = "inline";
390 break;
391 case PHP_HTTP_CONTENT_DISPOSITION_ATTACHMENT:
392 tmp = "attachment";
393 break;
394 }
395
396 if (tmp) {
397 if (SUCCESS != zend_hash_find(Z_ARRVAL_P(zoption_copy), ZEND_STRS("filename"), (void *) &zfilename)) {
398 ret = php_http_env_set_response_header_format(0, 1 TSRMLS_CC, "Content-Disposition: %s", tmp);
399 } else {
400 zval *zfilename_copy = php_http_ztyp(IS_STRING, *zfilename);
401
402 if (!Z_STRLEN_P(zfilename_copy)) {
403 ret = php_http_env_set_response_header_format(0, 1 TSRMLS_CC, "Content-Disposition: %s", tmp);
404 } else {
405 int new_f_len;
406 char *new_f_str = php_addslashes(estrndup(Z_STRVAL_P(zfilename_copy), Z_STRLEN_P(zfilename_copy)), Z_STRLEN_P(zfilename_copy), &new_f_len, 1 TSRMLS_CC);
407
408 ret = php_http_env_set_response_header_format(0, 1 TSRMLS_CC, "Content-Disposition: %s; filename=\"%.*s\"", tmp, new_f_len, new_f_str);
409 STR_FREE(new_f_str);
410 }
411
412 zval_ptr_dtor(&zfilename_copy);
413 }
414 }
415 zval_ptr_dtor(&zdisposition_copy);
416 }
417 zval_ptr_dtor(&zoption_copy);
418 }
419
420 if (ret != SUCCESS) {
421 return ret;
422 }
423
424 if ((zoption = get_option(options, ZEND_STRL("contentEncoding") TSRMLS_CC))) {
425 zval *zoption_copy = php_http_ztyp(IS_LONG, zoption);
426 zval zsupported;
427 HashTable *result = NULL;
428
429 zval_ptr_dtor(&zoption);
430 switch (Z_LVAL_P(zoption_copy)) {
431 case PHP_HTTP_CONTENT_ENCODING_GZIP:
432 INIT_PZVAL(&zsupported);
433 array_init(&zsupported);
434 add_next_index_stringl(&zsupported, ZEND_STRL("none"), 1);
435 add_next_index_stringl(&zsupported, ZEND_STRL("gzip"), 1);
436 add_next_index_stringl(&zsupported, ZEND_STRL("deflate"), 1);
437
438 if ((result = php_http_negotiate_encoding(Z_ARRVAL(zsupported) TSRMLS_CC))) {
439 char *key_str = NULL;
440 uint key_len = 0;
441
442 zend_hash_internal_pointer_reset(result);
443 if (HASH_KEY_IS_STRING == zend_hash_get_current_key_ex(result, &key_str, &key_len, NULL, 0, NULL)) {
444 if (!strcmp(key_str, "gzip")) {
445 if (!(r->content.encoder = php_http_encoding_stream_init(NULL, php_http_encoding_stream_get_deflate_ops(), PHP_HTTP_DEFLATE_TYPE_GZIP TSRMLS_CC))) {
446 ret = FAILURE;
447 } else if (SUCCESS == (ret = php_http_env_set_response_header(0, ZEND_STRL("Content-Encoding: gzip"), 1 TSRMLS_CC))) {
448 r->content.encoding = estrndup(key_str, key_len - 1);
449 }
450 } else if (!strcmp(key_str, "deflate")) {
451 if (!(r->content.encoder = php_http_encoding_stream_init(NULL, php_http_encoding_stream_get_deflate_ops(), PHP_HTTP_DEFLATE_TYPE_ZLIB TSRMLS_CC))) {
452 ret = FAILURE;
453 } else if (SUCCESS == (ret = php_http_env_set_response_header(0, ZEND_STRL("Content-Encoding: deflate"), 1 TSRMLS_CC))) {
454 r->content.encoding = estrndup(key_str, key_len - 1);
455 }
456 } else {
457 ret = php_http_env_set_response_header_value(0, ZEND_STRL("Content-Encoding"), NULL, 0 TSRMLS_CC);
458 }
459
460 if (SUCCESS == ret) {
461 ret = php_http_env_set_response_header(0, ZEND_STRL("Vary: Accept-Encoding"), 0 TSRMLS_CC);
462 }
463 }
464
465 zend_hash_destroy(result);
466 FREE_HASHTABLE(result);
467 }
468
469 zval_dtor(&zsupported);
470 break;
471
472 case PHP_HTTP_CONTENT_ENCODING_NONE:
473 default:
474 ret = php_http_env_set_response_header_value(0, ZEND_STRL("Content-Encoding"), NULL, 0 TSRMLS_CC);
475 break;
476 }
477 zval_ptr_dtor(&zoption_copy);
478 }
479
480 if (SUCCESS != ret) {
481 return ret;
482 }
483
484 if (php_http_env_get_response_code(TSRMLS_C) < 400 && !php_http_env_got_request_header(ZEND_STRL("Authorization") TSRMLS_CC)
485 && ( !SG(request_info).request_method
486 || !strcasecmp(SG(request_info).request_method, "GET")
487 || !strcasecmp(SG(request_info).request_method, "HEAD")
488 )
489 ) {
490 switch (php_http_env_is_response_cached_by_etag(options, ZEND_STRL("If-None-Match") TSRMLS_CC)) {
491 case PHP_HTTP_CACHE_MISS:
492 break;
493
494 case PHP_HTTP_CACHE_NO:
495 if (PHP_HTTP_CACHE_HIT != php_http_env_is_response_cached_by_last_modified(options, ZEND_STRL("If-Modified-Since") TSRMLS_CC)) {
496 break;
497 }
498 /* no break */
499
500 case PHP_HTTP_CACHE_HIT:
501 ret = php_http_env_set_response_code(304 TSRMLS_CC);
502 r->done = 1;
503 break;
504 }
505
506 if ((zoption = get_option(options, ZEND_STRL("etag") TSRMLS_CC))) {
507 zval *zoption_copy = php_http_ztyp(IS_STRING, zoption);
508
509 zval_ptr_dtor(&zoption);
510 if (*Z_STRVAL_P(zoption_copy) != '"' && strncmp(Z_STRVAL_P(zoption_copy), "W/\"", 3)) {
511 ret = php_http_env_set_response_header_format(0, 1 TSRMLS_CC, "ETag: \"%s\"", Z_STRVAL_P(zoption_copy));
512 } else {
513 ret = php_http_env_set_response_header_value(0, ZEND_STRL("ETag"), zoption_copy, 1 TSRMLS_CC);
514 }
515 zval_ptr_dtor(&zoption_copy);
516 }
517 if ((zoption = get_option(options, ZEND_STRL("lastModified") TSRMLS_CC))) {
518 zval *zoption_copy = php_http_ztyp(IS_LONG, zoption);
519
520 zval_ptr_dtor(&zoption);
521 if (Z_LVAL_P(zoption_copy)) {
522 char *date = php_format_date(ZEND_STRL(PHP_HTTP_DATE_FORMAT), Z_LVAL_P(zoption_copy), 0 TSRMLS_CC);
523 if (date) {
524 ret = php_http_env_set_response_header_format(0, 1 TSRMLS_CC, "Last-Modified: %s", date);
525 efree(date);
526 }
527 }
528 zval_ptr_dtor(&zoption_copy);
529 }
530 }
531 }
532
533 return ret;
534 }
535
536 static STATUS php_http_env_response_send_body(php_http_env_response_t *r)
537 {
538 STATUS ret = SUCCESS;
539 zval *zbody;
540 TSRMLS_FETCH_FROM_CTX(r->ts);
541
542 if (r->done) {
543 return ret;
544 }
545
546 if ( (zbody = get_option(r->options, ZEND_STRL("body") TSRMLS_CC))
547 && (Z_TYPE_P(zbody) == IS_OBJECT)
548 && instanceof_function(Z_OBJCE_P(zbody), php_http_message_body_class_entry TSRMLS_CC)
549 ) {
550 php_http_message_body_object_t *obj = zend_object_store_get_object(zbody TSRMLS_CC);
551
552 if (r->range.status == PHP_HTTP_RANGE_OK) {
553 if (zend_hash_num_elements(&r->range.values) == 1) {
554 /* single range */
555 zval **range, **begin, **end;
556
557 if (SUCCESS != zend_hash_index_find(&r->range.values, 0, (void *) &range)
558 || SUCCESS != zend_hash_index_find(Z_ARRVAL_PP(range), 0, (void *) &begin)
559 || SUCCESS != zend_hash_index_find(Z_ARRVAL_PP(range), 1, (void *) &end)
560 ) {
561 /* this should never happen */
562 zend_hash_destroy(&r->range.values);
563 php_http_env_set_response_code(500 TSRMLS_CC);
564 ret = FAILURE;
565 } else {
566 /* send chunk */
567 php_http_message_body_to_callback(obj->body, (php_http_pass_callback_t) php_http_env_response_send_data, r, Z_LVAL_PP(begin), Z_LVAL_PP(end) - Z_LVAL_PP(begin) + 1);
568 php_http_env_response_send_done(r);
569 zend_hash_destroy(&r->range.values);
570 ret = SUCCESS;
571 }
572 } else {
573 /* send multipart/byte-ranges message */
574 HashPosition pos;
575 zval **chunk;
576
577 FOREACH_HASH_VAL(pos, &r->range.values, chunk) {
578 zval **begin, **end;
579
580 if (IS_ARRAY == Z_TYPE_PP(chunk)
581 && SUCCESS == zend_hash_index_find(Z_ARRVAL_PP(chunk), 0, (void *) &begin)
582 && IS_LONG == Z_TYPE_PP(begin)
583 && SUCCESS == zend_hash_index_find(Z_ARRVAL_PP(chunk), 1, (void *) &end)
584 && IS_LONG == Z_TYPE_PP(end)
585 ) {
586 php_http_buffer_appendf(r->buffer,
587 PHP_HTTP_CRLF
588 "--%s" PHP_HTTP_CRLF
589 "Content-Type: %s" PHP_HTTP_CRLF
590 "Content-Range: bytes %ld-%ld/%zu" PHP_HTTP_CRLF PHP_HTTP_CRLF,
591 /* - */
592 r->range.boundary,
593 r->content.type ? r->content.type : "application/octet-stream",
594 Z_LVAL_PP(begin),
595 Z_LVAL_PP(end),
596 r->content.length
597 );
598 php_http_message_body_to_callback(obj->body, (php_http_pass_callback_t) php_http_env_response_send_data, r, Z_LVAL_PP(begin), Z_LVAL_PP(end) - Z_LVAL_PP(begin) + 1);
599 }
600 }
601 php_http_buffer_appendf(r->buffer, PHP_HTTP_CRLF "--%s--", r->range.boundary);
602 php_http_env_response_send_done(r);
603 zend_hash_destroy(&r->range.values);
604 }
605
606 } else {
607 php_http_message_body_to_callback(obj->body, (php_http_pass_callback_t) php_http_env_response_send_data, r, 0, 0);
608 php_http_env_response_send_done(r);
609 }
610 }
611 if (zbody) {
612 zval_ptr_dtor(&zbody);
613 }
614 return ret;
615 }
616
617 PHP_HTTP_API STATUS php_http_env_response_send(php_http_env_response_t *r)
618 {
619 zval *zbody;
620 TSRMLS_FETCH_FROM_CTX(r->ts);
621
622 /* check for ranges */
623 if ( (zbody = get_option(r->options, ZEND_STRL("body") TSRMLS_CC))
624 && (Z_TYPE_P(zbody) == IS_OBJECT)
625 && instanceof_function(Z_OBJCE_P(zbody), php_http_message_body_class_entry TSRMLS_CC)
626 ) {
627 php_http_message_body_object_t *obj = zend_object_store_get_object(zbody TSRMLS_CC);
628
629 r->content.length = php_http_message_body_size(obj->body);
630 zval_ptr_dtor(&zbody);
631
632 if (SUCCESS != php_http_env_set_response_header(0, ZEND_STRL("Accept-Ranges: bytes"), 1 TSRMLS_CC)) {
633 return FAILURE;
634 } else {
635 zend_hash_init(&r->range.values, 0, NULL, ZVAL_PTR_DTOR, 0);
636 r->range.status = php_http_env_get_request_ranges(&r->range.values, r->content.length TSRMLS_CC);
637
638 switch (r->range.status) {
639 case PHP_HTTP_RANGE_NO:
640 zend_hash_destroy(&r->range.values);
641 break;
642
643 case PHP_HTTP_RANGE_ERR:
644 if (php_http_env_got_request_header(ZEND_STRL("If-Range") TSRMLS_CC)) {
645 r->range.status = PHP_HTTP_RANGE_NO;
646 zend_hash_destroy(&r->range.values);
647 } else {
648 r->done = 1;
649 zend_hash_destroy(&r->range.values);
650 if (SUCCESS != php_http_env_set_response_header_format(416, 1 TSRMLS_CC, "Content-Range: bytes */%zu", r->content.length)) {
651 return FAILURE;
652 }
653 }
654 break;
655
656 case PHP_HTTP_RANGE_OK:
657 if (PHP_HTTP_CACHE_MISS == php_http_env_is_response_cached_by_etag(r->options, ZEND_STRL("If-Range") TSRMLS_CC)
658 || PHP_HTTP_CACHE_MISS == php_http_env_is_response_cached_by_last_modified(r->options, ZEND_STRL("If-Range") TSRMLS_CC)
659 ) {
660 r->range.status = PHP_HTTP_RANGE_NO;
661 zend_hash_destroy(&r->range.values);
662 break;
663 }
664 if (PHP_HTTP_CACHE_MISS == php_http_env_is_response_cached_by_etag(r->options, ZEND_STRL("If-Match") TSRMLS_CC)
665 || PHP_HTTP_CACHE_MISS == php_http_env_is_response_cached_by_last_modified(r->options, ZEND_STRL("If-Unmodified-Since") TSRMLS_CC)
666 || PHP_HTTP_CACHE_MISS == php_http_env_is_response_cached_by_last_modified(r->options, ZEND_STRL("Unless-Modified-Since") TSRMLS_CC)
667 ) {
668 r->done = 1;
669 zend_hash_destroy(&r->range.values);
670 if (SUCCESS != php_http_env_set_response_code(412 TSRMLS_CC)) {
671 return FAILURE;
672 }
673 break;
674 }
675
676 break;
677 }
678 }
679 } else if (zbody) {
680 zval_ptr_dtor(&zbody);
681 }
682
683 if (SUCCESS != php_http_env_response_send_head(r)) {
684 return FAILURE;
685 }
686
687 if (SUCCESS != php_http_env_response_send_body(r)) {
688 return FAILURE;
689 }
690 return SUCCESS;
691 }
692
693 zend_class_entry *php_http_env_response_class_entry;
694
695 #undef PHP_HTTP_BEGIN_ARGS
696 #undef PHP_HTTP_EMPTY_ARGS
697 #define PHP_HTTP_BEGIN_ARGS(method, req_args) PHP_HTTP_BEGIN_ARGS_EX(HttpEnvResponse, method, 0, req_args)
698 #define PHP_HTTP_EMPTY_ARGS(method) PHP_HTTP_EMPTY_ARGS_EX(HttpEnvResponse, method, 0)
699 #define PHP_HTTP_ENV_RESPONSE_ME(method, visibility) PHP_ME(HttpEnvResponse, method, PHP_HTTP_ARGS(HttpEnvResponse, method), visibility)
700
701 PHP_HTTP_EMPTY_ARGS(__construct);
702
703 PHP_HTTP_BEGIN_ARGS(setContentType, 1)
704 PHP_HTTP_ARG_VAL(content_type, 0)
705 PHP_HTTP_END_ARGS;
706
707 PHP_HTTP_BEGIN_ARGS(setContentEncoding, 1)
708 PHP_HTTP_ARG_VAL(content_encoding, 0)
709 PHP_HTTP_END_ARGS;
710
711 PHP_HTTP_BEGIN_ARGS(setContentDisposition, 1)
712 PHP_HTTP_ARG_VAL(content_disposition, 0)
713 PHP_HTTP_ARG_VAL(filename, 0)
714 PHP_HTTP_END_ARGS;
715
716 PHP_HTTP_BEGIN_ARGS(setCacheControl, 1)
717 PHP_HTTP_ARG_VAL(cache_control, 0)
718 PHP_HTTP_END_ARGS;
719
720 PHP_HTTP_BEGIN_ARGS(setLastModified, 1)
721 PHP_HTTP_ARG_VAL(last_modified, 0)
722 PHP_HTTP_END_ARGS;
723
724 PHP_HTTP_BEGIN_ARGS(isCachedByLastModified, 0)
725 PHP_HTTP_ARG_VAL(header_name, 0)
726 PHP_HTTP_END_ARGS;
727
728 PHP_HTTP_BEGIN_ARGS(setEtag, 1)
729 PHP_HTTP_ARG_VAL(etag, 0)
730 PHP_HTTP_END_ARGS;
731
732 PHP_HTTP_BEGIN_ARGS(isCachedByEtag, 0)
733 PHP_HTTP_ARG_VAL(header_name, 0)
734 PHP_HTTP_END_ARGS;
735
736 PHP_HTTP_BEGIN_ARGS(setThrottleRate, 1)
737 PHP_HTTP_ARG_VAL(chunk_size, 0)
738 PHP_HTTP_ARG_VAL(delay, 0)
739 PHP_HTTP_END_ARGS;
740
741 PHP_HTTP_EMPTY_ARGS(send);
742
743
744 zend_function_entry php_http_env_response_method_entry[] = {
745 PHP_HTTP_ENV_RESPONSE_ME(__construct, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
746 PHP_HTTP_ENV_RESPONSE_ME(setContentType, ZEND_ACC_PUBLIC)
747 PHP_HTTP_ENV_RESPONSE_ME(setContentDisposition, ZEND_ACC_PUBLIC)
748 PHP_HTTP_ENV_RESPONSE_ME(setContentEncoding, ZEND_ACC_PUBLIC)
749 PHP_HTTP_ENV_RESPONSE_ME(setCacheControl, ZEND_ACC_PUBLIC)
750 PHP_HTTP_ENV_RESPONSE_ME(setLastModified, ZEND_ACC_PUBLIC)
751 PHP_HTTP_ENV_RESPONSE_ME(isCachedByLastModified, ZEND_ACC_PUBLIC)
752 PHP_HTTP_ENV_RESPONSE_ME(setEtag, ZEND_ACC_PUBLIC)
753 PHP_HTTP_ENV_RESPONSE_ME(isCachedByEtag, ZEND_ACC_PUBLIC)
754 PHP_HTTP_ENV_RESPONSE_ME(setThrottleRate, ZEND_ACC_PUBLIC)
755
756 PHP_HTTP_ENV_RESPONSE_ME(send, ZEND_ACC_PUBLIC)
757
758 EMPTY_FUNCTION_ENTRY
759 };
760
761
762 PHP_METHOD(HttpEnvResponse, __construct)
763 {
764 with_error_handling(EH_THROW, php_http_exception_class_entry) {
765 if (SUCCESS == zend_parse_parameters_none()) {
766 php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
767
768 with_error_handling(EH_THROW, php_http_exception_class_entry) {
769 obj->message = php_http_message_init_env(obj->message, PHP_HTTP_RESPONSE TSRMLS_CC);
770 } end_error_handling();
771 }
772 } end_error_handling();
773
774 }
775
776 PHP_METHOD(HttpEnvResponse, setContentType)
777 {
778 char *ct_str = NULL;
779 int ct_len = 0;
780
781 if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s!", &ct_str, &ct_len)) {
782 set_option(getThis(), ZEND_STRL("contentType"), IS_STRING, ct_str, ct_len TSRMLS_CC);
783 }
784 }
785
786 PHP_METHOD(HttpEnvResponse, setContentDisposition)
787 {
788 long cd;
789 char *file_str = NULL;
790 int file_len = 0;
791
792 if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l|s!", &cd, &file_str, &file_len)) {
793 zval *arr;
794
795 MAKE_STD_ZVAL(arr);
796 array_init(arr);
797 add_assoc_long_ex(arr, ZEND_STRS("disposition"), cd);
798 if (file_len) {
799 add_assoc_stringl_ex(arr, ZEND_STRS("filename"), file_str, file_len, 1);
800 }
801 zend_update_property(Z_OBJCE_P(getThis()), getThis(), ZEND_STRL("contentDisposition"), arr TSRMLS_CC);
802 zval_ptr_dtor(&arr);
803 }
804 }
805
806 PHP_METHOD(HttpEnvResponse, setContentEncoding)
807 {
808 long ce;
809
810 if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &ce)) {
811 set_option(getThis(), ZEND_STRL("contentEncoding"), IS_LONG, &ce, 0 TSRMLS_CC);
812 }
813 }
814
815 PHP_METHOD(HttpEnvResponse, setCacheControl)
816 {
817 char *cc_str = NULL;
818 int cc_len = 0;
819
820 if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s!", &cc_str, &cc_len)) {
821 set_option(getThis(), ZEND_STRL("cacheControl"), IS_STRING, cc_str, cc_len TSRMLS_CC);
822 }
823 }
824
825 PHP_METHOD(HttpEnvResponse, setLastModified)
826 {
827 long last_modified;
828
829 if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &last_modified)) {
830 set_option(getThis(), ZEND_STRL("lastModified"), IS_LONG, &last_modified, 0 TSRMLS_CC);
831 }
832 }
833
834 PHP_METHOD(HttpEnvResponse, isCachedByLastModified)
835 {
836 char *header_name_str = NULL;
837 int header_name_len = 0;
838
839 if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s!", &header_name_str, &header_name_len)) {
840 if (!header_name_str || !header_name_len) {
841 header_name_str = "If-Modified-Since";
842 header_name_len = lenof("If-Modified-Since");
843 }
844 RETURN_LONG(php_http_env_is_response_cached_by_last_modified(getThis(), header_name_str, header_name_len TSRMLS_CC));
845 }
846 RETURN_FALSE;
847 }
848
849 PHP_METHOD(HttpEnvResponse, setEtag)
850 {
851 char *etag_str = NULL;
852 int etag_len = 0;
853
854 if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s!", &etag_str, &etag_len)) {
855 set_option(getThis(), ZEND_STRL("etag"), IS_STRING, etag_str, etag_len TSRMLS_CC);
856 }
857 }
858
859 PHP_METHOD(HttpEnvResponse, isCachedByEtag)
860 {
861 char *header_name_str = NULL;
862 int header_name_len = 0;
863
864 if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s!", &header_name_str, &header_name_len)) {
865 if (!header_name_str || !header_name_len) {
866 header_name_str = "If-None-Match";
867 header_name_len = lenof("If-None-Match");
868 }
869 RETURN_LONG(php_http_env_is_response_cached_by_etag(getThis(), header_name_str, header_name_len TSRMLS_CC));
870 }
871 RETURN_FALSE;
872 }
873
874 PHP_METHOD(HttpEnvResponse, setThrottleRate)
875 {
876 long chunk_size;
877 double delay = 1;
878
879 if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l|d", &chunk_size, &delay)) {
880 long chunk_size_long = (long) chunk_size;
881
882 set_option(getThis(), ZEND_STRL("throttleDelay"), IS_DOUBLE, &delay, 0 TSRMLS_CC);
883 set_option(getThis(), ZEND_STRL("throttleChunk"), IS_LONG, &chunk_size_long, 0 TSRMLS_CC);
884 RETURN_TRUE;
885 }
886 RETURN_FALSE;
887 }
888
889 PHP_METHOD(HttpEnvResponse, send)
890 {
891 RETVAL_FALSE;
892
893 if (SUCCESS == zend_parse_parameters_none()) {
894 php_http_env_response_t *r = php_http_env_response_init(NULL, getThis() TSRMLS_CC);
895
896 if (r) {
897 RETVAL_SUCCESS(php_http_env_response_send(r));
898 }
899
900 php_http_env_response_free(&r);
901 }
902 }
903
904 PHP_MINIT_FUNCTION(http_env_response)
905 {
906 PHP_HTTP_REGISTER_CLASS(http\\Env, Response, http_env_response, php_http_message_class_entry, 0);
907
908 zend_declare_class_constant_long(php_http_env_response_class_entry, ZEND_STRL("CONTENT_DISPOSITION_NONE"), PHP_HTTP_CONTENT_DISPOSITION_NONE TSRMLS_CC);
909 zend_declare_class_constant_long(php_http_env_response_class_entry, ZEND_STRL("CONTENT_DISPOSITION_INLINE"), PHP_HTTP_CONTENT_DISPOSITION_INLINE TSRMLS_CC);
910 zend_declare_class_constant_long(php_http_env_response_class_entry, ZEND_STRL("CONTENT_DISPOSITION_ATTACHMENT"), PHP_HTTP_CONTENT_DISPOSITION_ATTACHMENT TSRMLS_CC);
911
912 zend_declare_class_constant_long(php_http_env_response_class_entry, ZEND_STRL("CONTENT_ENCODING_NONE"), PHP_HTTP_CONTENT_ENCODING_NONE TSRMLS_CC);
913 zend_declare_class_constant_long(php_http_env_response_class_entry, ZEND_STRL("CONTENT_ENCODING_GZIP"), PHP_HTTP_CONTENT_ENCODING_GZIP TSRMLS_CC);
914
915 zend_declare_class_constant_long(php_http_env_response_class_entry, ZEND_STRL("CACHE_NO"), PHP_HTTP_CACHE_NO TSRMLS_CC);
916 zend_declare_class_constant_long(php_http_env_response_class_entry, ZEND_STRL("CACHE_HIT"), PHP_HTTP_CACHE_HIT TSRMLS_CC);
917 zend_declare_class_constant_long(php_http_env_response_class_entry, ZEND_STRL("CACHE_MISS"), PHP_HTTP_CACHE_MISS TSRMLS_CC);
918
919 zend_declare_property_null(php_http_env_response_class_entry, ZEND_STRL("contentType"), ZEND_ACC_PROTECTED TSRMLS_CC);
920 zend_declare_property_null(php_http_env_response_class_entry, ZEND_STRL("contentDisposition"), ZEND_ACC_PROTECTED TSRMLS_CC);
921 zend_declare_property_null(php_http_env_response_class_entry, ZEND_STRL("contentEncoding"), ZEND_ACC_PROTECTED TSRMLS_CC);
922 zend_declare_property_null(php_http_env_response_class_entry, ZEND_STRL("cacheControl"), ZEND_ACC_PROTECTED TSRMLS_CC);
923 zend_declare_property_null(php_http_env_response_class_entry, ZEND_STRL("etag"), ZEND_ACC_PROTECTED TSRMLS_CC);
924 zend_declare_property_null(php_http_env_response_class_entry, ZEND_STRL("lastModified"), ZEND_ACC_PROTECTED TSRMLS_CC);
925 zend_declare_property_null(php_http_env_response_class_entry, ZEND_STRL("throttleDelay"), ZEND_ACC_PROTECTED TSRMLS_CC);
926 zend_declare_property_null(php_http_env_response_class_entry, ZEND_STRL("throttleChunk"), ZEND_ACC_PROTECTED TSRMLS_CC);
927
928 return SUCCESS;
929 }
930
931
932 /*
933 * Local variables:
934 * tab-width: 4
935 * c-basic-offset: 4
936 * End:
937 * vim600: noet sw=4 ts=4 fdm=marker
938 * vim<600: noet sw=4 ts=4
939 */