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