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"
35 #include "php_http_encoding_api.h"
37 ZEND_EXTERN_MODULE_GLOBALS(http
);
39 #define http_flush() _http_flush(TSRMLS_C)
40 /* {{{ static inline void http_flush() */
41 static inline void _http_flush(TSRMLS_D
)
43 php_end_ob_buffer(1, 1 TSRMLS_CC
);
48 #define http_sleep() _http_sleep(TSRMLS_C)
49 /* {{{ static inline void http_sleep() */
50 static inline void _http_sleep(TSRMLS_D
)
52 #define HTTP_MSEC(s) (s * 1000)
53 #define HTTP_USEC(s) (HTTP_MSEC(s) * 1000)
54 #define HTTP_NSEC(s) (HTTP_USEC(s) * 1000)
55 #define HTTP_NANOSEC (1000 * 1000 * 1000)
56 #define HTTP_DIFFSEC (0.001)
58 if (HTTP_G(send
).throttle_delay
>= HTTP_DIFFSEC
) {
59 #if defined(PHP_WIN32)
60 Sleep((DWORD
) HTTP_MSEC(HTTP_G(send
).throttle_delay
));
61 #elif defined(HAVE_USLEEP)
62 usleep(HTTP_USEC(HTTP_G(send
).throttle_delay
));
63 #elif defined(HAVE_NANOSLEEP)
64 struct timespec req
, rem
;
66 req
.tv_sec
= (time_t) HTTP_G(send
).throttle_delay
;
67 req
.tv_nsec
= HTTP_NSEC(HTTP_G(send
).throttle_delay
) % HTTP_NANOSEC
;
69 while (nanosleep(&req
, &rem
) && (errno
== EINTR
) && (HTTP_NSEC(rem
.tv_sec
) + rem
.tv_nsec
) > HTTP_NSEC(HTTP_DIFFSEC
))) {
70 req
.tv_sec
= rem
.tv_sec
;
71 req
.tv_nsec
= rem
.tv_nsec
;
78 # define HTTP_CHUNK_ENCODE(data, size, dogzip) \
80 char *encoded = NULL; \
81 size_t encoded_len = 0; \
83 if (SUCCESS != http_encode(dogzip, 1, data, size, &encoded, &encoded_len)) { \
91 # define HTTP_CHUNK_ENCODE(data, size, dogzip)
94 #define HTTP_CHUNK_AVAIL(len, cs) ((len -= cs) >= 0)
95 #define HTTP_CHUNK_WRITE(d, l, dofree, dosleep, dogzip) \
97 long size = (long) l; \
98 char *data = (char *) d; \
100 HTTP_CHUNK_ENCODE(data, size, dogzip); \
102 if ((1 > size) || (size - PHPWRITE(data, size))) { \
115 #define http_send_chunk(d, b, e, m) _http_send_chunk((d), (b), (e), (m) TSRMLS_CC)
116 /* {{{ static STATUS http_send_chunk(const void *, size_t, size_t, http_send_mode) */
117 static STATUS
_http_send_chunk(const void *data
, size_t begin
, size_t end
, http_send_mode mode TSRMLS_DC
)
119 long len
= end
- begin
;
120 size_t chunk_size
= HTTP_G(send
).buffer_size
;
121 http_encoding_type gzip
= HTTP_G(send
).gzip_encoding
;
128 php_stream
*s
= (php_stream
*) data
;
130 if (php_stream_seek(s
, begin
, SEEK_SET
)) {
134 buf
= emalloc(HTTP_G(send
).buffer_size
);
136 while (HTTP_CHUNK_AVAIL(len
, chunk_size
)) {
137 HTTP_CHUNK_WRITE(buf
, php_stream_read(s
, buf
, chunk_size
), 1, 1, gzip
);
140 /* read & write left over */
142 HTTP_CHUNK_WRITE(buf
, php_stream_read(s
, buf
, chunk_size
+ len
), 1, 0, gzip
);
151 char *s
= (char *) data
+ begin
;
153 while (HTTP_CHUNK_AVAIL(len
, chunk_size
)) {
154 HTTP_CHUNK_WRITE(s
, chunk_size
, 0, 1, gzip
);
158 /* write left over */
160 HTTP_CHUNK_WRITE(s
, chunk_size
+ len
, 0, 0, gzip
);
173 /* {{{ STATUS http_send_header(char *, char *, zend_bool) */
174 PHP_HTTP_API STATUS
_http_send_header_ex(const char *name
, size_t name_len
, const char *value
, size_t value_len
, zend_bool replace
, char **sent_header TSRMLS_DC
)
177 size_t header_len
= sizeof(": ") + name_len
+ value_len
+ 1;
178 char *header
= emalloc(header_len
+ 1);
180 header
[header_len
] = '\0';
181 header_len
= snprintf(header
, header_len
, "%s: %s", name
, value
);
182 ret
= http_send_header_string_ex(header
, header_len
, replace
);
184 *sent_header
= header
;
192 /* {{{ STATUS http_send_status_header(int, char *) */
193 PHP_HTTP_API STATUS
_http_send_status_header_ex(int status
, const char *header
, size_t header_len
, zend_bool replace TSRMLS_DC
)
196 sapi_header_line h
= {(char *) header
, header_len
, status
};
197 if (SUCCESS
!= (ret
= sapi_header_op(replace
? SAPI_HEADER_REPLACE
: SAPI_HEADER_ADD
, &h TSRMLS_CC
))) {
198 http_error_ex(HE_WARNING
, HTTP_E_HEADER
, "Could not send header: %s (%d)", header
, status
);
204 /* {{{ STATUS http_send_last_modified(int) */
205 PHP_HTTP_API STATUS
_http_send_last_modified_ex(time_t t
, char **sent_header TSRMLS_DC
)
208 char *date
= http_date(t
);
214 ret
= http_send_header_ex("Last-Modified", lenof("Last-Modified"), date
, strlen(date
), 1, sent_header
);
218 HTTP_G(send
).last_modified
= t
;
224 /* {{{ STATUS http_send_etag(char *, size_t) */
225 PHP_HTTP_API STATUS
_http_send_etag_ex(const char *etag
, size_t etag_len
, char **sent_header TSRMLS_DC
)
231 http_error_ex(HE_WARNING
, HTTP_E_HEADER
, "Attempt to send empty ETag (previous: %s)\n", HTTP_G(send
).unquoted_etag
);
236 STR_SET(HTTP_G(send
).unquoted_etag
, estrndup(etag
, etag_len
));
238 etag_len
= spprintf(&etag_header
, 0, "ETag: \"%s\"", etag
);
239 status
= http_send_header_string_ex(etag_header
, etag_len
, 1);
242 *sent_header
= etag_header
;
251 /* {{{ STATUS http_send_content_type(char *, size_t) */
252 PHP_HTTP_API STATUS
_http_send_content_type(const char *content_type
, size_t ct_len TSRMLS_DC
)
254 HTTP_CHECK_CONTENT_TYPE(content_type
, return FAILURE
);
256 /* remember for multiple ranges */
257 STR_FREE(HTTP_G(send
).content_type
);
258 HTTP_G(send
).content_type
= estrndup(content_type
, ct_len
);
260 return http_send_header_ex("Content-Type", lenof("Content-Type"), content_type
, ct_len
, 1, NULL
);
264 /* {{{ STATUS http_send_content_disposition(char *, size_t, zend_bool) */
265 PHP_HTTP_API STATUS
_http_send_content_disposition(const char *filename
, size_t f_len
, zend_bool send_inline TSRMLS_DC
)
271 cd_header
= ecalloc(1, sizeof("Content-Disposition: inline; filename=\"\"") + f_len
);
272 sprintf(cd_header
, "Content-Disposition: inline; filename=\"%s\"", filename
);
274 cd_header
= ecalloc(1, sizeof("Content-Disposition: attachment; filename=\"\"") + f_len
);
275 sprintf(cd_header
, "Content-Disposition: attachment; filename=\"%s\"", filename
);
278 status
= http_send_header_string(cd_header
);
284 /* {{{ STATUS http_send_ranges(HashTable *, void *, size_t, http_send_mode) */
285 PHP_HTTP_API STATUS
_http_send_ranges(HashTable
*ranges
, const void *data
, size_t size
, http_send_mode mode TSRMLS_DC
)
287 zval
**zbegin
, **zend
, **zrange
;
290 if (zend_hash_num_elements(ranges
) == 1) {
291 char range_header
[256] = {0};
293 if (SUCCESS
!= zend_hash_index_find(ranges
, 0, (void **) &zrange
) ||
294 SUCCESS
!= zend_hash_index_find(Z_ARRVAL_PP(zrange
), 0, (void **) &zbegin
) ||
295 SUCCESS
!= zend_hash_index_find(Z_ARRVAL_PP(zrange
), 1, (void **) &zend
)) {
296 http_send_status(500);
300 /* Send HTTP 206 Partial Content */
301 http_send_status(206);
303 /* send content range header */
304 snprintf(range_header
, 255, "Content-Range: bytes %ld-%ld/%lu", Z_LVAL_PP(zbegin
), Z_LVAL_PP(zend
), (ulong
) size
);
305 http_send_header_string(range_header
);
307 /* send requested chunk */
308 return http_send_chunk(data
, Z_LVAL_PP(zbegin
), Z_LVAL_PP(zend
) + 1, mode
);
314 char bound
[23] = {0}, preface
[1024] = {0},
315 multi_header
[68] = "Content-Type: multipart/byteranges; boundary=";
317 /* Send HTTP 206 Partial Content */
318 http_send_status(206);
320 /* send multipart/byteranges header */
321 snprintf(bound
, 22, "--%lu%0.9f", (ulong
) time(NULL
), php_combined_lcg(TSRMLS_C
));
322 strncat(multi_header
, bound
+ 2, 21);
323 http_send_header_string(multi_header
);
325 /* send each requested chunk */
326 FOREACH_HASH_VAL(ranges
, zrange
) {
327 if (SUCCESS
!= zend_hash_index_find(Z_ARRVAL_PP(zrange
), 0, (void **) &zbegin
) ||
328 SUCCESS
!= zend_hash_index_find(Z_ARRVAL_PP(zrange
), 1, (void **) &zend
)) {
332 preface_len
= snprintf(preface
, 1023,
334 HTTP_CRLF
"Content-Type: %s"
335 HTTP_CRLF
"Content-Range: bytes %ld-%ld/%lu"
340 HTTP_G(send
).content_type
? HTTP_G(send
).content_type
: "application/x-octetstream",
346 PHPWRITE(preface
, preface_len
);
347 http_send_chunk(data
, Z_LVAL_PP(zbegin
), Z_LVAL_PP(zend
) + 1, mode
);
350 /* write boundary once more */
351 PHPWRITE(HTTP_CRLF
, lenof(HTTP_CRLF
));
352 PHPWRITE(bound
, strlen(bound
));
353 PHPWRITE("--", lenof("--"));
360 /* {{{ STATUS http_send(void *, size_t, http_send_mode) */
361 PHP_HTTP_API STATUS
_http_send_ex(const void *data_ptr
, size_t data_size
, http_send_mode data_mode
, zend_bool no_cache TSRMLS_DC
)
364 http_range_status range_status
;
365 int cache_etag
= 0, external_gzip_handlers
= 0;
374 /* stop on-the-fly etag generation */
375 cache_etag
= http_interrupt_ob_etaghandler();
377 if ( php_ob_handler_used("ob_gzhandler" TSRMLS_CC
) ||
378 php_ob_handler_used("zlib output compression" TSRMLS_CC
)) {
379 external_gzip_handlers
= 1;
381 /* enable partial dl and resume */
382 http_send_header_string("Accept-Ranges: bytes");
384 zend_hash_init(&ranges
, 0, NULL
, ZVAL_PTR_DTOR
, 0);
385 range_status
= http_get_request_ranges(&ranges
, data_size
);
387 if (range_status
== RANGE_ERR
) {
388 zend_hash_destroy(&ranges
);
389 http_send_status(416);
393 /* Range Request - only send ranges if entity hasn't changed */
394 if ( range_status
== RANGE_OK
&&
395 http_match_etag_ex("HTTP_IF_MATCH", HTTP_G(send
).unquoted_etag
, 0) &&
396 http_match_last_modified_ex("HTTP_IF_UNMODIFIED_SINCE", HTTP_G(send
).last_modified
, 0) &&
397 http_match_last_modified_ex("HTTP_UNLESS_MODIFIED_SINCE", HTTP_G(send
).last_modified
, 0)) {
398 STATUS result
= http_send_ranges(&ranges
, data_ptr
, data_size
, data_mode
);
399 zend_hash_destroy(&ranges
);
403 zend_hash_destroy(&ranges
);
405 /* send 304 Not Modified if etag matches - DON'T return on ETag generation failure */
406 if (!no_cache
&& cache_etag
) {
409 if (etag
= http_etag(data_ptr
, data_size
, data_mode
)) {
410 char *sent_header
= NULL
;
412 http_send_etag_ex(etag
, strlen(etag
), &sent_header
);
413 if (http_match_etag("HTTP_IF_NONE_MATCH", etag
)) {
414 return http_exit_ex(304, sent_header
, NULL
, 0);
416 STR_FREE(sent_header
);
422 /* send 304 Not Modified if last modified matches */
423 if (!no_cache
&& http_match_last_modified("HTTP_IF_MODIFIED_SINCE", HTTP_G(send
).last_modified
)) {
424 char *sent_header
= NULL
;
425 http_send_last_modified_ex(HTTP_G(send
).last_modified
, &sent_header
);
426 return http_exit_ex(304, sent_header
, NULL
, 0);
429 if (external_gzip_handlers
) {
430 #ifdef HTTP_HAVE_ZLIB
431 if (HTTP_G(send
).gzip_encoding
) {
432 HTTP_G(send
).gzip_encoding
= 0;
434 } else if (HTTP_G(send
).gzip_encoding
) {
438 INIT_PZVAL(&zsupported
);
439 array_init(&zsupported
);
440 add_next_index_stringl(&zsupported
, "gzip", lenof("gzip"), 1);
441 add_next_index_stringl(&zsupported
, "deflate", lenof("deflate"), 1);
442 add_next_index_stringl(&zsupported
, "compress", lenof("compress"), 1);
444 if (selected
= http_negotiate_encoding(&zsupported
)) {
445 char *encoding
= NULL
;
448 if (HASH_KEY_IS_STRING
== zend_hash_get_current_key(selected
, &encoding
, &idx
, 0) && encoding
) {
451 if (!strcmp(encoding
, "gzip")) {
452 if (SUCCESS
== (hs
= http_send_header_string("Content-Encoding: gzip"))) {
453 HTTP_G(send
).gzip_encoding
= HTTP_ENCODING_GZIP
;
455 } else if (!strcmp(encoding
, "deflate")) {
456 if (SUCCESS
== (hs
= http_send_header_string("Content-Encoding: deflate"))) {
457 HTTP_G(send
).gzip_encoding
= HTTP_ENCODING_DEFLATE
;
459 } else if (!strcmp(encoding
, "compress")) {
460 if (SUCCESS
== (hs
= http_send_header_string("Content-Encoding: compress"))) {
461 HTTP_G(send
).gzip_encoding
= HTTP_ENCODING_COMPRESS
;
465 http_send_header_string("Vary: Accept-Encoding");
467 HTTP_G(send
).gzip_encoding
= 0;
471 zend_hash_destroy(selected
);
472 FREE_HASHTABLE(selected
);
475 zval_dtor(&zsupported
);
478 /* emit a content-length header */
480 spprintf(&cl
, 0, "Content-Length: %lu", (unsigned long) data_size
);
481 http_send_header_string(cl
);
485 /* send full entity */
486 return http_send_chunk(data_ptr
, 0, data_size
, data_mode
);
490 /* {{{ STATUS http_send_stream(php_stream *) */
491 PHP_HTTP_API STATUS
_http_send_stream_ex(php_stream
*file
, zend_bool close_stream
, zend_bool no_cache TSRMLS_DC
)
494 php_stream_statbuf ssb
;
496 if ((!file
) || php_stream_stat(file
, &ssb
)) {
500 status
= http_send_ex(file
, ssb
.sb
.st_size
, SEND_RSRC
, no_cache
);
503 php_stream_close(file
);
515 * vim600: sw=4 ts=4 fdm=marker