- smarter check for allowed methods
[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 sapi_header_line h = {(char *) header, strlen(header), status};
128 return sapi_header_op(SAPI_HEADER_REPLACE, &h TSRMLS_CC);
129 }
130 /* }}} */
131
132 /* {{{ STATUS http_send_last_modified(int) */
133 PHP_HTTP_API STATUS _http_send_last_modified(time_t t TSRMLS_DC)
134 {
135 char *date = NULL;
136 if (date = http_date(t)) {
137 char modified[96] = "Last-Modified: ";
138 strcat(modified, date);
139 efree(date);
140
141 /* remember */
142 HTTP_G(lmod) = t;
143
144 return http_send_header(modified);
145 }
146 return FAILURE;
147 }
148 /* }}} */
149
150 /* {{{ STATUS http_send_etag(char *, size_t) */
151 PHP_HTTP_API STATUS _http_send_etag(const char *etag, size_t etag_len TSRMLS_DC)
152 {
153 STATUS status;
154 char *etag_header;
155
156 if (!etag_len){
157 php_error_docref(NULL TSRMLS_CC,E_ERROR,
158 "Attempt to send empty ETag (previous: %s)\n", HTTP_G(etag));
159 return FAILURE;
160 }
161
162 /* remember */
163 if (HTTP_G(etag)) {
164 efree(HTTP_G(etag));
165 }
166 HTTP_G(etag) = estrdup(etag);
167
168 etag_header = ecalloc(1, sizeof("ETag: \"\"") + etag_len);
169 sprintf(etag_header, "ETag: \"%s\"", etag);
170 if (SUCCESS != (status = http_send_header(etag_header))) {
171 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Couldn't send '%s' header", etag_header);
172 }
173 efree(etag_header);
174 return status;
175 }
176 /* }}} */
177
178 /* {{{ STATUS http_send_cache_control(char *, size_t) */
179 PHP_HTTP_API STATUS _http_send_cache_control(const char *cache_control, size_t cc_len TSRMLS_DC)
180 {
181 STATUS status;
182 char *cc_header = ecalloc(1, sizeof("Cache-Control: ") + cc_len);
183
184 sprintf(cc_header, "Cache-Control: %s", cache_control);
185 if (SUCCESS != (status = http_send_header(cc_header))) {
186 php_error_docref(NULL TSRMLS_CC, E_NOTICE,
187 "Could not send '%s' header", cc_header);
188 }
189 efree(cc_header);
190 return status;
191 }
192 /* }}} */
193
194 /* {{{ STATUS http_send_content_type(char *, size_t) */
195 PHP_HTTP_API STATUS _http_send_content_type(const char *content_type, size_t ct_len TSRMLS_DC)
196 {
197 STATUS status;
198 char *ct_header;
199
200 if (!strchr(content_type, '/')) {
201 php_error_docref(NULL TSRMLS_CC, E_WARNING,
202 "Content-Type '%s' doesn't seem to consist of a primary and a secondary part",
203 content_type);
204 return FAILURE;
205 }
206
207 /* remember for multiple ranges */
208 if (HTTP_G(ctype)) {
209 efree(HTTP_G(ctype));
210 }
211 HTTP_G(ctype) = estrndup(content_type, ct_len);
212
213 ct_header = ecalloc(1, sizeof("Content-Type: ") + ct_len);
214 sprintf(ct_header, "Content-Type: %s", content_type);
215
216 if (SUCCESS != (status = http_send_header(ct_header))) {
217 php_error_docref(NULL TSRMLS_CC, E_WARNING,
218 "Couldn't send '%s' header", ct_header);
219 }
220 efree(ct_header);
221 return status;
222 }
223 /* }}} */
224
225 /* {{{ STATUS http_send_content_disposition(char *, size_t, zend_bool) */
226 PHP_HTTP_API STATUS _http_send_content_disposition(const char *filename, size_t f_len, zend_bool send_inline TSRMLS_DC)
227 {
228 STATUS status;
229 char *cd_header;
230
231 if (send_inline) {
232 cd_header = ecalloc(1, sizeof("Content-Disposition: inline; filename=\"\"") + f_len);
233 sprintf(cd_header, "Content-Disposition: inline; filename=\"%s\"", filename);
234 } else {
235 cd_header = ecalloc(1, sizeof("Content-Disposition: attachment; filename=\"\"") + f_len);
236 sprintf(cd_header, "Content-Disposition: attachment; filename=\"%s\"", filename);
237 }
238
239 if (SUCCESS != (status = http_send_header(cd_header))) {
240 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Couldn't send '%s' header", cd_header);
241 }
242 efree(cd_header);
243 return status;
244 }
245 /* }}} */
246
247 /* {{{ STATUS http_send_ranges(HashTable *, void *, size_t, http_send_mode) */
248 PHP_HTTP_API STATUS _http_send_ranges(HashTable *ranges, const void *data, size_t size, http_send_mode mode TSRMLS_DC)
249 {
250 long **begin, **end;
251 zval **zrange;
252
253 /* single range */
254 if (zend_hash_num_elements(ranges) == 1) {
255 char range_header[256] = {0};
256
257 if (SUCCESS != zend_hash_index_find(ranges, 0, (void **) &zrange) ||
258 SUCCESS != zend_hash_index_find(Z_ARRVAL_PP(zrange), 0, (void **) &begin) ||
259 SUCCESS != zend_hash_index_find(Z_ARRVAL_PP(zrange), 1, (void **) &end)) {
260 return FAILURE;
261 }
262
263 /* Send HTTP 206 Partial Content */
264 http_send_status(206);
265
266 /* send content range header */
267 snprintf(range_header, 255, "Content-Range: bytes %d-%d/%d", **begin, **end, size);
268 http_send_header(range_header);
269
270 /* send requested chunk */
271 return http_send_chunk(data, **begin, **end + 1, mode);
272 }
273
274 /* multi range */
275 else {
276 char bound[23] = {0}, preface[1024] = {0},
277 multi_header[68] = "Content-Type: multipart/byteranges; boundary=";
278
279 /* Send HTTP 206 Partial Content */
280 http_send_status(206);
281
282 /* send multipart/byteranges header */
283 snprintf(bound, 22, "--%d%0.9f", time(NULL), php_combined_lcg(TSRMLS_C));
284 strncat(multi_header, bound + 2, 21);
285 http_send_header(multi_header);
286
287 /* send each requested chunk */
288 FOREACH_HASH_VAL(ranges, zrange) {
289 if (SUCCESS != zend_hash_index_find(Z_ARRVAL_PP(zrange), 0, (void **) &begin) ||
290 SUCCESS != zend_hash_index_find(Z_ARRVAL_PP(zrange), 1, (void **) &end)) {
291 break;
292 }
293
294 snprintf(preface, 1023,
295 HTTP_CRLF "%s"
296 HTTP_CRLF "Content-Type: %s"
297 HTTP_CRLF "Content-Range: bytes %ld-%ld/%ld"
298 HTTP_CRLF
299 HTTP_CRLF,
300
301 bound,
302 HTTP_G(ctype) ? HTTP_G(ctype) : "application/x-octetstream",
303 **begin,
304 **end,
305 size
306 );
307
308 php_body_write(preface, strlen(preface) TSRMLS_CC);
309 http_send_chunk(data, **begin, **end + 1, mode);
310 }
311
312 /* write boundary once more */
313 php_body_write(HTTP_CRLF, sizeof(HTTP_CRLF) - 1 TSRMLS_CC);
314 php_body_write(bound, strlen(bound) TSRMLS_CC);
315 php_body_write("--", 2 TSRMLS_CC);
316
317 return SUCCESS;
318 }
319 }
320 /* }}} */
321
322 /* {{{ STATUS http_send(void *, size_t, http_send_mode) */
323 PHP_HTTP_API STATUS _http_send(const void *data_ptr, size_t data_size, http_send_mode data_mode TSRMLS_DC)
324 {
325 HashTable ranges;
326 http_range_status range_status;
327 int cache_etag = 0;
328
329 if (!data_ptr) {
330 return FAILURE;
331 }
332 if (!data_size) {
333 return SUCCESS;
334 }
335
336 /* stop on-the-fly etag generation */
337 if (cache_etag = HTTP_G(etag_started)) {
338 /* interrupt */
339 HTTP_G(etag_started) = 0;
340 /* never ever use the output to compute the ETag if http_send() is used */
341 php_end_ob_buffers(0 TSRMLS_CC);
342 }
343
344 zend_hash_init(&ranges, 0, NULL, ZVAL_PTR_DTOR, 0);
345 range_status = http_get_request_ranges(&ranges, data_size);
346
347 if (range_status == RANGE_ERR) {
348 zend_hash_destroy(&ranges);
349 http_send_status(416);
350 return FAILURE;
351 }
352
353 /* Range Request - only send ranges if entity hasn't changed */
354 if ( range_status == RANGE_OK &&
355 http_etag_match_ex("HTTP_IF_MATCH", HTTP_G(etag), 0) &&
356 http_modified_match_ex("HTTP_IF_UNMODIFIED_SINCE", HTTP_G(lmod), 0)) {
357 STATUS result = http_send_ranges(&ranges, data_ptr, data_size, data_mode);
358 zend_hash_destroy(&ranges);
359 return result;
360 }
361
362 zend_hash_destroy(&ranges);
363
364 /* send 304 Not Modified if etag matches */
365 if (cache_etag) {
366 char *etag = NULL;
367 int etag_match = 0;
368
369 if (!(etag = http_etag(data_ptr, data_size, data_mode))) {
370 return FAILURE;
371 }
372
373 http_send_etag(etag, 32);
374 etag_match = http_etag_match("HTTP_IF_NONE_MATCH", etag);
375 efree(etag);
376
377 if (etag_match) {
378 return http_send_status(304);
379 }
380 }
381
382 /* send 304 Not Modified if last modified matches */
383 if (http_modified_match("HTTP_IF_MODIFIED_SINCE", HTTP_G(lmod))) {
384 return http_send_status(304);
385 }
386
387 /* send full entity */
388 return http_send_chunk(data_ptr, 0, data_size, data_mode);
389 }
390 /* }}} */
391
392 /* {{{ STATUS http_send_stream(php_stream *) */
393 PHP_HTTP_API STATUS _http_send_stream_ex(php_stream *file, zend_bool close_stream TSRMLS_DC)
394 {
395 STATUS status;
396
397 if ((!file) || php_stream_stat(file, &HTTP_G(ssb))) {
398 return FAILURE;
399 }
400
401 status = http_send(file, HTTP_G(ssb).sb.st_size, SEND_RSRC);
402
403 if (close_stream) {
404 php_stream_close(file);
405 }
406
407 return status;
408 }
409 /* }}} */
410
411
412 /*
413 * Local variables:
414 * tab-width: 4
415 * c-basic-offset: 4
416 * End:
417 * vim600: sw=4 ts=4 fdm=marker
418 * vim<600: sw=4 ts=4
419 */
420