- prepare 0.8.0
[m6w6/ext-http] / http_send_api.c
1 /*
2 +----------------------------------------------------------------------+
3 | PECL :: http |
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 +----------------------------------------------------------------------+
14 */
15
16 /* $Id$ */
17
18 #ifdef HAVE_CONFIG_H
19 # include "config.h"
20 #endif
21
22 #include "php.h"
23 #include "php_streams.h"
24 #include "ext/standard/php_lcg.h"
25 #include "SAPI.h"
26
27 #include "php_http.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
36 ZEND_EXTERN_MODULE_GLOBALS(http);
37
38 #define http_flush() _http_flush(TSRMLS_C)
39 /* {{{ static inline void http_flush() */
40 static inline void _http_flush(TSRMLS_D)
41 {
42 php_end_ob_buffer(1, 1 TSRMLS_CC);
43 sapi_flush(TSRMLS_C);
44 }
45 /* }}} */
46
47 #define http_sleep() _http_sleep(TSRMLS_C)
48 /* {{{ static inline void http_sleep() */
49 static inline void _http_sleep(TSRMLS_D)
50 {
51 if (HTTP_G(send).throttle_delay >= 0.001) {
52 #if defined(PHP_WIN32)
53 Sleep((DWORD) (HTTP_G(send).throttle_delay * 1000));
54 #elif defined(HAVE_USLEEP)
55 usleep(HTTP_G(send).throttle_delay * 1000000);
56 #elif defined(HAVE_NANOSLEEP)
57 struct timespec req, rem;
58
59 req.tv_sec = (time_t) HTTP_G(send).throttle_delay;
60 req.tv_nsec = (HTTP_G(send).throttle_delay * 1000000000) % 1000000000;
61
62 while (nanosleep(&req, &rem) && (errno == EINTR) && (rem.tv_nsec > 1000000)) {
63 req.tv_sec = rem.tv_sec;
64 req.tv_nsec = rem.tv_nsec;
65 }
66 #endif
67 }
68 }
69 /* }}} */
70
71 #define HTTP_CHUNK_AVAIL(len) ((len -= HTTP_G(send).buffer_size) >= 0)
72 #define HTTP_CHUNK_WRITE(data, l, dofree, dosleep) \
73 { \
74 long size = (long) l; \
75 \
76 if ((1 > size) || (size - PHPWRITE(data, size))) { \
77 if (dofree) { \
78 efree(data); \
79 } \
80 return FAILURE; \
81 } \
82 \
83 http_flush(); \
84 if (dosleep) { \
85 http_sleep(); \
86 } \
87 }
88
89 #define http_send_chunk(d, b, e, m) _http_send_chunk((d), (b), (e), (m) TSRMLS_CC)
90 /* {{{ static STATUS http_send_chunk(const void *, size_t, size_t, http_send_mode) */
91 static STATUS _http_send_chunk(const void *data, size_t begin, size_t end, http_send_mode mode TSRMLS_DC)
92 {
93 long len = end - begin;
94
95 switch (mode)
96 {
97 case SEND_RSRC:
98 {
99 char *buf;
100 php_stream *s = (php_stream *) data;
101
102 if (php_stream_seek(s, begin, SEEK_SET)) {
103 return FAILURE;
104 }
105
106 buf = emalloc(HTTP_G(send).buffer_size);
107
108 while (HTTP_CHUNK_AVAIL(len)) {
109 HTTP_CHUNK_WRITE(buf, php_stream_read(s, buf, HTTP_G(send).buffer_size), 1, 1);
110 }
111
112 /* read & write left over */
113 if (len) {
114 HTTP_CHUNK_WRITE(buf, php_stream_read(s, buf, HTTP_G(send).buffer_size + len), 1, 0);
115 }
116
117 efree(buf);
118 return SUCCESS;
119 }
120
121 case SEND_DATA:
122 {
123 char *s = (char *) data + begin;
124
125 while (HTTP_CHUNK_AVAIL(len)) {
126 HTTP_CHUNK_WRITE(s, HTTP_G(send).buffer_size, 0, 1);
127 s += HTTP_G(send).buffer_size;
128 }
129
130 /* write left over */
131 if (len) {
132 HTTP_CHUNK_WRITE(s, HTTP_G(send).buffer_size + len, 0, 0);
133 }
134
135 return SUCCESS;
136 }
137
138 default:
139 return FAILURE;
140 break;
141 }
142 }
143 /* }}} */
144
145
146 /* {{{ STATUS http_send_status_header(int, char *) */
147 PHP_HTTP_API STATUS _http_send_status_header_ex(int status, const char *header, zend_bool replace TSRMLS_DC)
148 {
149 STATUS ret;
150 sapi_header_line h = {(char *) header, header ? strlen(header) : 0, status};
151 if (SUCCESS != (ret = sapi_header_op(replace ? SAPI_HEADER_REPLACE : SAPI_HEADER_ADD, &h TSRMLS_CC))) {
152 http_error_ex(E_WARNING, HTTP_E_HEADER, "Could not send header: %s (%d)", header, status);
153 }
154 return ret;
155 }
156 /* }}} */
157
158 /* {{{ STATUS http_send_last_modified(int) */
159 PHP_HTTP_API STATUS _http_send_last_modified(time_t t TSRMLS_DC)
160 {
161 char *date = NULL;
162 if (date = http_date(t)) {
163 char modified[96] = "Last-Modified: ";
164 strcat(modified, date);
165 efree(date);
166
167 /* remember */
168 HTTP_G(send).last_modified = t;
169
170 return http_send_header(modified);
171 }
172 return FAILURE;
173 }
174 /* }}} */
175
176 /* {{{ STATUS http_send_etag(char *, size_t) */
177 PHP_HTTP_API STATUS _http_send_etag(const char *etag, size_t etag_len TSRMLS_DC)
178 {
179 STATUS status;
180 char *etag_header;
181
182 if (!etag_len){
183 http_error_ex(E_WARNING, HTTP_E_HEADER, "Attempt to send empty ETag (previous: %s)\n", HTTP_G(send).unquoted_etag);
184 return FAILURE;
185 }
186
187 /* remember */
188 STR_FREE(HTTP_G(send).unquoted_etag);
189 HTTP_G(send).unquoted_etag = estrdup(etag);
190
191 etag_header = ecalloc(1, sizeof("ETag: \"\"") + etag_len);
192 sprintf(etag_header, "ETag: \"%s\"", etag);
193 status = http_send_header(etag_header);
194 efree(etag_header);
195 return status;
196 }
197 /* }}} */
198
199 /* {{{ STATUS http_send_cache_control(char *, size_t) */
200 PHP_HTTP_API STATUS _http_send_cache_control(const char *cache_control, size_t cc_len TSRMLS_DC)
201 {
202 STATUS status;
203 char *cc_header = ecalloc(1, sizeof("Cache-Control: ") + cc_len);
204
205 sprintf(cc_header, "Cache-Control: %s", cache_control);
206 status = http_send_header(cc_header);
207 efree(cc_header);
208 return status;
209 }
210 /* }}} */
211
212 /* {{{ STATUS http_send_content_type(char *, size_t) */
213 PHP_HTTP_API STATUS _http_send_content_type(const char *content_type, size_t ct_len TSRMLS_DC)
214 {
215 STATUS status;
216 char *ct_header;
217
218 if (!strchr(content_type, '/')) {
219 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);
220 return FAILURE;
221 }
222
223 /* remember for multiple ranges */
224 STR_FREE(HTTP_G(send).content_type);
225 HTTP_G(send).content_type = estrndup(content_type, ct_len);
226
227 ct_header = ecalloc(1, sizeof("Content-Type: ") + ct_len);
228 sprintf(ct_header, "Content-Type: %s", content_type);
229 status = http_send_header(ct_header);
230 efree(ct_header);
231 return status;
232 }
233 /* }}} */
234
235 /* {{{ STATUS http_send_content_disposition(char *, size_t, zend_bool) */
236 PHP_HTTP_API STATUS _http_send_content_disposition(const char *filename, size_t f_len, zend_bool send_inline TSRMLS_DC)
237 {
238 STATUS status;
239 char *cd_header;
240
241 if (send_inline) {
242 cd_header = ecalloc(1, sizeof("Content-Disposition: inline; filename=\"\"") + f_len);
243 sprintf(cd_header, "Content-Disposition: inline; filename=\"%s\"", filename);
244 } else {
245 cd_header = ecalloc(1, sizeof("Content-Disposition: attachment; filename=\"\"") + f_len);
246 sprintf(cd_header, "Content-Disposition: attachment; filename=\"%s\"", filename);
247 }
248
249 status = http_send_header(cd_header);
250 efree(cd_header);
251 return status;
252 }
253 /* }}} */
254
255 /* {{{ STATUS http_send_ranges(HashTable *, void *, size_t, http_send_mode) */
256 PHP_HTTP_API STATUS _http_send_ranges(HashTable *ranges, const void *data, size_t size, http_send_mode mode TSRMLS_DC)
257 {
258 long **begin, **end;
259 zval **zrange;
260
261 /* single range */
262 if (zend_hash_num_elements(ranges) == 1) {
263 char range_header[256] = {0};
264
265 if (SUCCESS != zend_hash_index_find(ranges, 0, (void **) &zrange) ||
266 SUCCESS != zend_hash_index_find(Z_ARRVAL_PP(zrange), 0, (void **) &begin) ||
267 SUCCESS != zend_hash_index_find(Z_ARRVAL_PP(zrange), 1, (void **) &end)) {
268 http_send_status(500);
269 return FAILURE;
270 }
271
272 /* Send HTTP 206 Partial Content */
273 http_send_status(206);
274
275 /* send content range header */
276 snprintf(range_header, 255, "Content-Range: bytes %d-%d/%d", **begin, **end, size);
277 http_send_header(range_header);
278
279 /* send requested chunk */
280 return http_send_chunk(data, **begin, **end + 1, mode);
281 }
282
283 /* multi range */
284 else {
285 char bound[23] = {0}, preface[1024] = {0},
286 multi_header[68] = "Content-Type: multipart/byteranges; boundary=";
287
288 /* Send HTTP 206 Partial Content */
289 http_send_status(206);
290
291 /* send multipart/byteranges header */
292 snprintf(bound, 22, "--%d%0.9f", time(NULL), php_combined_lcg(TSRMLS_C));
293 strncat(multi_header, bound + 2, 21);
294 http_send_header(multi_header);
295
296 /* send each requested chunk */
297 FOREACH_HASH_VAL(ranges, zrange) {
298 if (SUCCESS != zend_hash_index_find(Z_ARRVAL_PP(zrange), 0, (void **) &begin) ||
299 SUCCESS != zend_hash_index_find(Z_ARRVAL_PP(zrange), 1, (void **) &end)) {
300 break;
301 }
302
303 snprintf(preface, 1023,
304 HTTP_CRLF "%s"
305 HTTP_CRLF "Content-Type: %s"
306 HTTP_CRLF "Content-Range: bytes %ld-%ld/%ld"
307 HTTP_CRLF
308 HTTP_CRLF,
309
310 bound,
311 HTTP_G(send).content_type ? HTTP_G(send).content_type : "application/x-octetstream",
312 **begin,
313 **end,
314 size
315 );
316
317 PHPWRITE(preface, strlen(preface));
318 http_send_chunk(data, **begin, **end + 1, mode);
319 }
320
321 /* write boundary once more */
322 PHPWRITE(HTTP_CRLF, lenof(HTTP_CRLF));
323 PHPWRITE(bound, strlen(bound));
324 PHPWRITE("--", lenof("--"));
325
326 return SUCCESS;
327 }
328 }
329 /* }}} */
330
331 /* {{{ STATUS http_send(void *, size_t, http_send_mode) */
332 PHP_HTTP_API STATUS _http_send(const void *data_ptr, size_t data_size, http_send_mode data_mode TSRMLS_DC)
333 {
334 HashTable ranges;
335 http_range_status range_status;
336 int cache_etag = 0;
337
338 if (!data_ptr) {
339 return FAILURE;
340 }
341 if (!data_size) {
342 return SUCCESS;
343 }
344
345 /* stop on-the-fly etag generation */
346 if (cache_etag = HTTP_G(etag).started) {
347 /* interrupt ob_etaghandler */
348 HTTP_G(etag).started = 0;
349 }
350
351 /* enable partial dl and resume */
352 http_send_header("Accept-Ranges: bytes");
353
354 zend_hash_init(&ranges, 0, NULL, ZVAL_PTR_DTOR, 0);
355 range_status = http_get_request_ranges(&ranges, data_size);
356
357 if (range_status == RANGE_ERR) {
358 zend_hash_destroy(&ranges);
359 http_send_status(416);
360 return FAILURE;
361 }
362
363 /* Range Request - only send ranges if entity hasn't changed */
364 if ( range_status == RANGE_OK &&
365 http_match_etag_ex("HTTP_IF_MATCH", HTTP_G(send).unquoted_etag, 0) &&
366 http_match_last_modified_ex("HTTP_IF_UNMODIFIED_SINCE", HTTP_G(send).last_modified, 0)) {
367 STATUS result = http_send_ranges(&ranges, data_ptr, data_size, data_mode);
368 zend_hash_destroy(&ranges);
369 return result;
370 }
371
372 zend_hash_destroy(&ranges);
373
374 /* send 304 Not Modified if etag matches - DON'T return on ETag generation failure */
375 if (cache_etag) {
376 char *etag = NULL;
377
378 if (!(etag = http_etag(data_ptr, data_size, data_mode))) {
379 http_error(E_NOTICE, HTTP_E_PARAM, "Failed to generate ETag for data source");
380 } else {
381 http_send_etag(etag, 32);
382 if (http_match_etag("HTTP_IF_NONE_MATCH", etag)) {
383 return http_cache_exit_ex(etag, 1, 1);
384 }
385 efree(etag);
386 }
387 }
388
389 /* send 304 Not Modified if last modified matches */
390 if (http_match_last_modified("HTTP_IF_MODIFIED_SINCE", HTTP_G(send).last_modified)) {
391 return http_cache_exit_ex(http_date(HTTP_G(send).last_modified), 0, 1);
392 }
393
394 /* send full entity */
395 return http_send_chunk(data_ptr, 0, data_size, data_mode);
396 }
397 /* }}} */
398
399 /* {{{ STATUS http_send_stream(php_stream *) */
400 PHP_HTTP_API STATUS _http_send_stream_ex(php_stream *file, zend_bool close_stream TSRMLS_DC)
401 {
402 STATUS status;
403 php_stream_statbuf ssb;
404
405 if ((!file) || php_stream_stat(file, &ssb)) {
406 return FAILURE;
407 }
408
409 status = http_send(file, ssb.sb.st_size, SEND_RSRC);
410
411 if (close_stream) {
412 php_stream_close(file);
413 }
414
415 return status;
416 }
417 /* }}} */
418
419 /*
420 * Local variables:
421 * tab-width: 4
422 * c-basic-offset: 4
423 * End:
424 * vim600: sw=4 ts=4 fdm=marker
425 * vim<600: sw=4 ts=4
426 */
427