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 +----------------------------------------------------------------------+
24 #include "php_streams.h"
25 #include "ext/standard/php_lcg.h"
28 #include "php_http_std_defs.h"
29 #include "php_http_api.h"
30 #include "php_http_date_api.h"
31 #include "php_http_send_api.h"
32 #include "php_http_headers_api.h"
33 #include "php_http_date_api.h"
34 #include "php_http_cache_api.h"
36 ZEND_EXTERN_MODULE_GLOBALS(http
);
38 #define http_flush() _http_flush(TSRMLS_C)
39 /* {{{ static inline void http_flush() */
40 static inline void _http_flush(TSRMLS_D
)
42 php_end_ob_buffer(1, 1 TSRMLS_CC
);
47 #define http_sleep() _http_sleep(TSRMLS_C)
48 /* {{{ static inline void http_sleep() */
49 static inline void _http_sleep(TSRMLS_D
)
51 #define HTTP_MSEC(s) (s * 1000)
52 #define HTTP_USEC(s) (HTTP_MSEC(s) * 1000)
53 #define HTTP_NSEC(s) (HTTP_USEC(s) * 1000)
54 #define HTTP_NANOSEC (1000 * 1000 * 1000)
55 #define HTTP_DIFFSEC (0.001)
57 if (HTTP_G(send
).throttle_delay
>= HTTP_DIFFSEC
) {
58 #if defined(PHP_WIN32)
59 Sleep((DWORD
) HTTP_MSEC(HTTP_G(send
).throttle_delay
));
60 #elif defined(HAVE_USLEEP)
61 usleep(HTTP_USEC(HTTP_G(send
).throttle_delay
));
62 #elif defined(HAVE_NANOSLEEP)
63 struct timespec req
, rem
;
65 req
.tv_sec
= (time_t) HTTP_G(send
).throttle_delay
;
66 req
.tv_nsec
= HTTP_NSEC(HTTP_G(send
).throttle_delay
) % HTTP_NANOSEC
;
68 while (nanosleep(&req
, &rem
) && (errno
== EINTR
) && (HTTP_NSEC(rem
.tv_sec
) + rem
.tv_nsec
) > HTTP_NSEC(HTTP_DIFFSEC
))) {
69 req
.tv_sec
= rem
.tv_sec
;
70 req
.tv_nsec
= rem
.tv_nsec
;
77 #define HTTP_CHUNK_AVAIL(len) ((len -= HTTP_G(send).buffer_size) >= 0)
78 #define HTTP_CHUNK_WRITE(data, l, dofree, dosleep) \
80 long size = (long) l; \
82 if ((1 > size) || (size - PHPWRITE(data, size))) { \
95 #define http_send_chunk(d, b, e, m) _http_send_chunk((d), (b), (e), (m) TSRMLS_CC)
96 /* {{{ static STATUS http_send_chunk(const void *, size_t, size_t, http_send_mode) */
97 static STATUS
_http_send_chunk(const void *data
, size_t begin
, size_t end
, http_send_mode mode TSRMLS_DC
)
99 long len
= end
- begin
;
106 php_stream
*s
= (php_stream
*) data
;
108 if (php_stream_seek(s
, begin
, SEEK_SET
)) {
112 buf
= emalloc(HTTP_G(send
).buffer_size
);
114 while (HTTP_CHUNK_AVAIL(len
)) {
115 HTTP_CHUNK_WRITE(buf
, php_stream_read(s
, buf
, HTTP_G(send
).buffer_size
), 1, 1);
118 /* read & write left over */
120 HTTP_CHUNK_WRITE(buf
, php_stream_read(s
, buf
, HTTP_G(send
).buffer_size
+ len
), 1, 0);
129 char *s
= (char *) data
+ begin
;
131 while (HTTP_CHUNK_AVAIL(len
)) {
132 HTTP_CHUNK_WRITE(s
, HTTP_G(send
).buffer_size
, 0, 1);
133 s
+= HTTP_G(send
).buffer_size
;
136 /* write left over */
138 HTTP_CHUNK_WRITE(s
, HTTP_G(send
).buffer_size
+ len
, 0, 0);
152 /* {{{ STATUS http_send_status_header(int, char *) */
153 PHP_HTTP_API STATUS
_http_send_status_header_ex(int status
, const char *header
, zend_bool replace TSRMLS_DC
)
156 sapi_header_line h
= {(char *) header
, header
? strlen(header
) : 0, status
};
157 if (SUCCESS
!= (ret
= sapi_header_op(replace
? SAPI_HEADER_REPLACE
: SAPI_HEADER_ADD
, &h TSRMLS_CC
))) {
158 http_error_ex(E_WARNING
, HTTP_E_HEADER
, "Could not send header: %s (%d)", header
, status
);
164 /* {{{ STATUS http_send_last_modified(int) */
165 PHP_HTTP_API STATUS
_http_send_last_modified(time_t t TSRMLS_DC
)
168 if (date
= http_date(t
)) {
169 char modified
[96] = "Last-Modified: ";
170 strcat(modified
, date
);
174 HTTP_G(send
).last_modified
= t
;
176 return http_send_header(modified
);
182 /* {{{ STATUS http_send_etag(char *, size_t) */
183 PHP_HTTP_API STATUS
_http_send_etag(const char *etag
, size_t etag_len TSRMLS_DC
)
189 http_error_ex(E_WARNING
, HTTP_E_HEADER
, "Attempt to send empty ETag (previous: %s)\n", HTTP_G(send
).unquoted_etag
);
194 STR_FREE(HTTP_G(send
).unquoted_etag
);
195 HTTP_G(send
).unquoted_etag
= estrdup(etag
);
197 etag_header
= ecalloc(1, sizeof("ETag: \"\"") + etag_len
);
198 sprintf(etag_header
, "ETag: \"%s\"", etag
);
199 status
= http_send_header(etag_header
);
205 /* {{{ STATUS http_send_cache_control(char *, size_t) */
206 PHP_HTTP_API STATUS
_http_send_cache_control(const char *cache_control
, size_t cc_len TSRMLS_DC
)
209 char *cc_header
= ecalloc(1, sizeof("Cache-Control: ") + cc_len
);
211 sprintf(cc_header
, "Cache-Control: %s", cache_control
);
212 status
= http_send_header(cc_header
);
218 /* {{{ STATUS http_send_content_type(char *, size_t) */
219 PHP_HTTP_API STATUS
_http_send_content_type(const char *content_type
, size_t ct_len TSRMLS_DC
)
224 if (!strchr(content_type
, '/')) {
225 http_error_ex(E_WARNING
, HTTP_E_PARAM
, "Content-Type '%s' doesn't seem to consist of a primary and a secondary part", content_type
);
229 /* remember for multiple ranges */
230 STR_FREE(HTTP_G(send
).content_type
);
231 HTTP_G(send
).content_type
= estrndup(content_type
, ct_len
);
233 ct_header
= ecalloc(1, sizeof("Content-Type: ") + ct_len
);
234 sprintf(ct_header
, "Content-Type: %s", content_type
);
235 status
= http_send_header(ct_header
);
241 /* {{{ STATUS http_send_content_disposition(char *, size_t, zend_bool) */
242 PHP_HTTP_API STATUS
_http_send_content_disposition(const char *filename
, size_t f_len
, zend_bool send_inline TSRMLS_DC
)
248 cd_header
= ecalloc(1, sizeof("Content-Disposition: inline; filename=\"\"") + f_len
);
249 sprintf(cd_header
, "Content-Disposition: inline; filename=\"%s\"", filename
);
251 cd_header
= ecalloc(1, sizeof("Content-Disposition: attachment; filename=\"\"") + f_len
);
252 sprintf(cd_header
, "Content-Disposition: attachment; filename=\"%s\"", filename
);
255 status
= http_send_header(cd_header
);
261 /* {{{ STATUS http_send_ranges(HashTable *, void *, size_t, http_send_mode) */
262 PHP_HTTP_API STATUS
_http_send_ranges(HashTable
*ranges
, const void *data
, size_t size
, http_send_mode mode TSRMLS_DC
)
268 if (zend_hash_num_elements(ranges
) == 1) {
269 char range_header
[256] = {0};
271 if (SUCCESS
!= zend_hash_index_find(ranges
, 0, (void **) &zrange
) ||
272 SUCCESS
!= zend_hash_index_find(Z_ARRVAL_PP(zrange
), 0, (void **) &begin
) ||
273 SUCCESS
!= zend_hash_index_find(Z_ARRVAL_PP(zrange
), 1, (void **) &end
)) {
274 http_send_status(500);
278 /* Send HTTP 206 Partial Content */
279 http_send_status(206);
281 /* send content range header */
282 snprintf(range_header
, 255, "Content-Range: bytes %d-%d/%d", **begin
, **end
, size
);
283 http_send_header(range_header
);
285 /* send requested chunk */
286 return http_send_chunk(data
, **begin
, **end
+ 1, mode
);
291 char bound
[23] = {0}, preface
[1024] = {0},
292 multi_header
[68] = "Content-Type: multipart/byteranges; boundary=";
294 /* Send HTTP 206 Partial Content */
295 http_send_status(206);
297 /* send multipart/byteranges header */
298 snprintf(bound
, 22, "--%d%0.9f", time(NULL
), php_combined_lcg(TSRMLS_C
));
299 strncat(multi_header
, bound
+ 2, 21);
300 http_send_header(multi_header
);
302 /* send each requested chunk */
303 FOREACH_HASH_VAL(ranges
, zrange
) {
304 if (SUCCESS
!= zend_hash_index_find(Z_ARRVAL_PP(zrange
), 0, (void **) &begin
) ||
305 SUCCESS
!= zend_hash_index_find(Z_ARRVAL_PP(zrange
), 1, (void **) &end
)) {
309 snprintf(preface
, 1023,
311 HTTP_CRLF
"Content-Type: %s"
312 HTTP_CRLF
"Content-Range: bytes %ld-%ld/%ld"
317 HTTP_G(send
).content_type
? HTTP_G(send
).content_type
: "application/x-octetstream",
323 PHPWRITE(preface
, strlen(preface
));
324 http_send_chunk(data
, **begin
, **end
+ 1, mode
);
327 /* write boundary once more */
328 PHPWRITE(HTTP_CRLF
, lenof(HTTP_CRLF
));
329 PHPWRITE(bound
, strlen(bound
));
330 PHPWRITE("--", lenof("--"));
337 /* {{{ STATUS http_send(void *, size_t, http_send_mode) */
338 PHP_HTTP_API STATUS
_http_send(const void *data_ptr
, size_t data_size
, http_send_mode data_mode TSRMLS_DC
)
341 http_range_status range_status
;
351 /* stop on-the-fly etag generation */
352 if (cache_etag
= HTTP_G(etag
).started
) {
353 /* interrupt ob_etaghandler */
354 HTTP_G(etag
).started
= 0;
357 /* enable partial dl and resume */
358 http_send_header("Accept-Ranges: bytes");
360 zend_hash_init(&ranges
, 0, NULL
, ZVAL_PTR_DTOR
, 0);
361 range_status
= http_get_request_ranges(&ranges
, data_size
);
363 if (range_status
== RANGE_ERR
) {
364 zend_hash_destroy(&ranges
);
365 http_send_status(416);
369 /* Range Request - only send ranges if entity hasn't changed */
370 if ( range_status
== RANGE_OK
&&
371 http_match_etag_ex("HTTP_IF_MATCH", HTTP_G(send
).unquoted_etag
, 0) &&
372 http_match_last_modified_ex("HTTP_IF_UNMODIFIED_SINCE", HTTP_G(send
).last_modified
, 0)) {
373 STATUS result
= http_send_ranges(&ranges
, data_ptr
, data_size
, data_mode
);
374 zend_hash_destroy(&ranges
);
378 zend_hash_destroy(&ranges
);
380 /* send 304 Not Modified if etag matches - DON'T return on ETag generation failure */
384 if (!(etag
= http_etag(data_ptr
, data_size
, data_mode
))) {
385 http_error(E_NOTICE
, HTTP_E_PARAM
, "Failed to generate ETag for data source");
387 http_send_etag(etag
, 32);
388 if (http_match_etag("HTTP_IF_NONE_MATCH", etag
)) {
389 return http_cache_exit_ex(etag
, 1, 1);
395 /* send 304 Not Modified if last modified matches */
396 if (http_match_last_modified("HTTP_IF_MODIFIED_SINCE", HTTP_G(send
).last_modified
)) {
397 return http_cache_exit_ex(http_date(HTTP_G(send
).last_modified
), 0, 1);
400 /* send full entity */
401 return http_send_chunk(data_ptr
, 0, data_size
, data_mode
);
405 /* {{{ STATUS http_send_stream(php_stream *) */
406 PHP_HTTP_API STATUS
_http_send_stream_ex(php_stream
*file
, zend_bool close_stream TSRMLS_DC
)
409 php_stream_statbuf ssb
;
411 if ((!file
) || php_stream_stat(file
, &ssb
)) {
415 status
= http_send(file
, ssb
.sb
.st_size
, SEND_RSRC
);
418 php_stream_close(file
);
430 * vim600: sw=4 ts=4 fdm=marker