2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | This source file is subject to version 3.0 of the PHP license, that |
6 | is bundled with this package in the file LICENSE, and is available |
7 | through the world-wide-web at http://www.php.net/license/3_0.txt. |
8 | If you did not receive a copy of the PHP license and are unable to |
9 | obtain it through the world-wide-web, please send a note to |
10 | license@php.net so we can mail you a copy immediately. |
11 +----------------------------------------------------------------------+
12 | Copyright (c) 2004-2005 Michael Wallner <mike@php.net> |
13 +----------------------------------------------------------------------+
23 #include "php_streams.h"
24 #include "ext/standard/php_lcg.h"
28 #include "php_http_std_defs.h"
29 #include "php_http_api.h"
30 #include "php_http_send_api.h"
31 #include "php_http_headers_api.h"
32 #include "php_http_date_api.h"
33 #include "php_http_cache_api.h"
35 ZEND_EXTERN_MODULE_GLOBALS(http
);
37 #define http_send_chunk(d, b, e, m) _http_send_chunk((d), (b), (e), (m) TSRMLS_CC)
38 /* {{{ static STATUS http_send_chunk(const void *, size_t, size_t, http_send_mode) */
39 static STATUS
_http_send_chunk(const void *data
, size_t begin
, size_t end
, http_send_mode mode TSRMLS_DC
)
41 long len
= end
- begin
;
49 php_stream
*s
= (php_stream
*) data
;
51 if (php_stream_seek(s
, begin
, SEEK_SET
)) {
55 buf
= (char *) ecalloc(1, HTTP_SENDBUF_SIZE
);
56 /* read into buf and write out */
57 while ((len
-= HTTP_SENDBUF_SIZE
) >= 0) {
58 if (!(read
= php_stream_read(s
, buf
, HTTP_SENDBUF_SIZE
))) {
62 if (read
- php_body_write(buf
, read TSRMLS_CC
)) {
66 /* ob_flush() && flush() */
67 php_end_ob_buffer(1, 1 TSRMLS_CC
);
71 /* read & write left over */
73 if (read
= php_stream_read(s
, buf
, HTTP_SENDBUF_SIZE
+ len
)) {
74 if (read
- php_body_write(buf
, read TSRMLS_CC
)) {
82 /* ob_flush() & flush() */
83 php_end_ob_buffer(1, 1 TSRMLS_CC
);
92 char *s
= (char *) data
+ begin
;
94 while ((len
-= HTTP_SENDBUF_SIZE
) >= 0) {
95 if (HTTP_SENDBUF_SIZE
- php_body_write(s
, HTTP_SENDBUF_SIZE TSRMLS_CC
)) {
98 s
+= HTTP_SENDBUF_SIZE
;
99 /* ob_flush() & flush() */
100 php_end_ob_buffer(1, 1 TSRMLS_CC
);
101 sapi_flush(TSRMLS_C
);
104 /* write left over */
106 if (HTTP_SENDBUF_SIZE
+ len
- php_body_write(s
, HTTP_SENDBUF_SIZE
+ len TSRMLS_CC
)) {
109 /* ob_flush() & flush() */
110 php_end_ob_buffer(1, 1 TSRMLS_CC
);
111 sapi_flush(TSRMLS_C
);
124 /* {{{ STATUS http_send_status_header(int, char *) */
125 PHP_HTTP_API STATUS
_http_send_status_header(int status
, const char *header TSRMLS_DC
)
127 sapi_header_line h
= {(char *) header
, strlen(header
), status
};
128 return sapi_header_op(SAPI_HEADER_REPLACE
, &h TSRMLS_CC
);
132 /* {{{ STATUS http_send_last_modified(int) */
133 PHP_HTTP_API STATUS
_http_send_last_modified(time_t t TSRMLS_DC
)
136 if (date
= http_date(t
)) {
137 char modified
[96] = "Last-Modified: ";
138 strcat(modified
, date
);
144 return http_send_header(modified
);
150 /* {{{ STATUS http_send_etag(char *, size_t) */
151 PHP_HTTP_API STATUS
_http_send_etag(const char *etag
, size_t etag_len TSRMLS_DC
)
157 php_error_docref(NULL TSRMLS_CC
,E_ERROR
,
158 "Attempt to send empty ETag (previous: %s)\n", HTTP_G(etag
));
166 HTTP_G(etag
) = estrdup(etag
);
168 etag_header
= ecalloc(1, sizeof("ETag: \"\"") + etag_len
);
169 sprintf(etag_header
, "ETag: \"%s\"", etag
);
170 if (SUCCESS
!= (status
= http_send_header(etag_header
))) {
171 php_error_docref(NULL TSRMLS_CC
, E_WARNING
, "Couldn't send '%s' header", etag_header
);
178 /* {{{ STATUS http_send_cache_control(char *, size_t) */
179 PHP_HTTP_API STATUS
_http_send_cache_control(const char *cache_control
, size_t cc_len TSRMLS_DC
)
182 char *cc_header
= ecalloc(1, sizeof("Cache-Control: ") + cc_len
);
184 sprintf(cc_header
, "Cache-Control: %s", cache_control
);
185 if (SUCCESS
!= (status
= http_send_header(cc_header
))) {
186 php_error_docref(NULL TSRMLS_CC
, E_NOTICE
,
187 "Could not send '%s' header", cc_header
);
194 /* {{{ STATUS http_send_content_type(char *, size_t) */
195 PHP_HTTP_API STATUS
_http_send_content_type(const char *content_type
, size_t ct_len TSRMLS_DC
)
200 if (!strchr(content_type
, '/')) {
201 php_error_docref(NULL TSRMLS_CC
, E_WARNING
,
202 "Content-Type '%s' doesn't seem to consist of a primary and a secondary part",
207 /* remember for multiple ranges */
209 efree(HTTP_G(ctype
));
211 HTTP_G(ctype
) = estrndup(content_type
, ct_len
);
213 ct_header
= ecalloc(1, sizeof("Content-Type: ") + ct_len
);
214 sprintf(ct_header
, "Content-Type: %s", content_type
);
216 if (SUCCESS
!= (status
= http_send_header(ct_header
))) {
217 php_error_docref(NULL TSRMLS_CC
, E_WARNING
,
218 "Couldn't send '%s' header", ct_header
);
225 /* {{{ STATUS http_send_content_disposition(char *, size_t, zend_bool) */
226 PHP_HTTP_API STATUS
_http_send_content_disposition(const char *filename
, size_t f_len
, zend_bool send_inline TSRMLS_DC
)
232 cd_header
= ecalloc(1, sizeof("Content-Disposition: inline; filename=\"\"") + f_len
);
233 sprintf(cd_header
, "Content-Disposition: inline; filename=\"%s\"", filename
);
235 cd_header
= ecalloc(1, sizeof("Content-Disposition: attachment; filename=\"\"") + f_len
);
236 sprintf(cd_header
, "Content-Disposition: attachment; filename=\"%s\"", filename
);
239 if (SUCCESS
!= (status
= http_send_header(cd_header
))) {
240 php_error_docref(NULL TSRMLS_CC
, E_WARNING
, "Couldn't send '%s' header", cd_header
);
247 /* {{{ STATUS http_send_ranges(HashTable *, void *, size_t, http_send_mode) */
248 PHP_HTTP_API STATUS
_http_send_ranges(HashTable
*ranges
, const void *data
, size_t size
, http_send_mode mode TSRMLS_DC
)
254 if (zend_hash_num_elements(ranges
) == 1) {
255 char range_header
[256] = {0};
257 if (SUCCESS
!= zend_hash_index_find(ranges
, 0, (void **) &zrange
) ||
258 SUCCESS
!= zend_hash_index_find(Z_ARRVAL_PP(zrange
), 0, (void **) &begin
) ||
259 SUCCESS
!= zend_hash_index_find(Z_ARRVAL_PP(zrange
), 1, (void **) &end
)) {
263 /* Send HTTP 206 Partial Content */
264 http_send_status(206);
266 /* send content range header */
267 snprintf(range_header
, 255, "Content-Range: bytes %d-%d/%d", **begin
, **end
, size
);
268 http_send_header(range_header
);
270 /* send requested chunk */
271 return http_send_chunk(data
, **begin
, **end
+ 1, mode
);
276 char bound
[23] = {0}, preface
[1024] = {0},
277 multi_header
[68] = "Content-Type: multipart/byteranges; boundary=";
279 /* Send HTTP 206 Partial Content */
280 http_send_status(206);
282 /* send multipart/byteranges header */
283 snprintf(bound
, 22, "--%d%0.9f", time(NULL
), php_combined_lcg(TSRMLS_C
));
284 strncat(multi_header
, bound
+ 2, 21);
285 http_send_header(multi_header
);
287 /* send each requested chunk */
288 FOREACH_HASH_VAL(ranges
, zrange
) {
289 if (SUCCESS
!= zend_hash_index_find(Z_ARRVAL_PP(zrange
), 0, (void **) &begin
) ||
290 SUCCESS
!= zend_hash_index_find(Z_ARRVAL_PP(zrange
), 1, (void **) &end
)) {
294 snprintf(preface
, 1023,
296 HTTP_CRLF
"Content-Type: %s"
297 HTTP_CRLF
"Content-Range: bytes %ld-%ld/%ld"
302 HTTP_G(ctype
) ? HTTP_G(ctype
) : "application/x-octetstream",
308 php_body_write(preface
, strlen(preface
) TSRMLS_CC
);
309 http_send_chunk(data
, **begin
, **end
+ 1, mode
);
312 /* write boundary once more */
313 php_body_write(HTTP_CRLF
, sizeof(HTTP_CRLF
) - 1 TSRMLS_CC
);
314 php_body_write(bound
, strlen(bound
) TSRMLS_CC
);
315 php_body_write("--", 2 TSRMLS_CC
);
322 /* {{{ STATUS http_send(void *, size_t, http_send_mode) */
323 PHP_HTTP_API STATUS
_http_send(const void *data_ptr
, size_t data_size
, http_send_mode data_mode TSRMLS_DC
)
326 http_range_status range_status
;
336 /* stop on-the-fly etag generation */
337 if (cache_etag
= HTTP_G(etag_started
)) {
339 HTTP_G(etag_started
) = 0;
340 /* never ever use the output to compute the ETag if http_send() is used */
341 php_end_ob_buffers(0 TSRMLS_CC
);
344 zend_hash_init(&ranges
, 0, NULL
, ZVAL_PTR_DTOR
, 0);
345 range_status
= http_get_request_ranges(&ranges
, data_size
);
347 if (range_status
== RANGE_ERR
) {
348 zend_hash_destroy(&ranges
);
349 http_send_status(416);
353 /* Range Request - only send ranges if entity hasn't changed */
354 if ( range_status
== RANGE_OK
&&
355 http_etag_match_ex("HTTP_IF_MATCH", HTTP_G(etag
), 0) &&
356 http_modified_match_ex("HTTP_IF_UNMODIFIED_SINCE", HTTP_G(lmod
), 0)) {
357 STATUS result
= http_send_ranges(&ranges
, data_ptr
, data_size
, data_mode
);
358 zend_hash_destroy(&ranges
);
362 zend_hash_destroy(&ranges
);
364 /* send 304 Not Modified if etag matches */
369 if (!(etag
= http_etag(data_ptr
, data_size
, data_mode
))) {
373 http_send_etag(etag
, 32);
374 etag_match
= http_etag_match("HTTP_IF_NONE_MATCH", etag
);
378 return http_send_status(304);
382 /* send 304 Not Modified if last modified matches */
383 if (http_modified_match("HTTP_IF_MODIFIED_SINCE", HTTP_G(lmod
))) {
384 return http_send_status(304);
387 /* send full entity */
388 return http_send_chunk(data_ptr
, 0, data_size
, data_mode
);
392 /* {{{ STATUS http_send_stream(php_stream *) */
393 PHP_HTTP_API STATUS
_http_send_stream_ex(php_stream
*file
, zend_bool close_stream TSRMLS_DC
)
397 if ((!file
) || php_stream_stat(file
, &HTTP_G(ssb
))) {
401 status
= http_send(file
, HTTP_G(ssb
).sb
.st_size
, SEND_RSRC
);
404 php_stream_close(file
);
417 * vim600: sw=4 ts=4 fdm=marker