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