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