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