rename info tests
[m6w6/ext-http] / php_http_filter.c
1 /*
2 +--------------------------------------------------------------------+
3 | PECL :: http |
4 +--------------------------------------------------------------------+
5 | Redistribution and use in source and binary forms, with or without |
6 | modification, are permitted provided that the conditions mentioned |
7 | in the accompanying LICENSE file are met. |
8 +--------------------------------------------------------------------+
9 | Copyright (c) 2004-2014, Michael Wallner <mike@php.net> |
10 +--------------------------------------------------------------------+
11 */
12
13 #include "php_http_api.h"
14
15 #ifndef DBG_FILTER
16 # define DBG_FILTER 0
17 #endif
18
19 PHP_MINIT_FUNCTION(http_filter)
20 {
21 php_stream_filter_register_factory("http.*", &php_http_filter_factory TSRMLS_CC);
22 return SUCCESS;
23 }
24
25 #define PHP_HTTP_FILTER_PARAMS \
26 php_stream *stream, \
27 php_stream_filter *this, \
28 php_stream_bucket_brigade *buckets_in, \
29 php_stream_bucket_brigade *buckets_out, \
30 size_t *bytes_consumed, int flags \
31 TSRMLS_DC
32 #define PHP_HTTP_FILTER_OP(filter) \
33 http_filter_op_ ##filter
34 #define PHP_HTTP_FILTER_OPS(filter) \
35 php_stream_filter_ops PHP_HTTP_FILTER_OP(filter)
36 #define PHP_HTTP_FILTER_DTOR(filter) \
37 http_filter_ ##filter## _dtor
38 #define PHP_HTTP_FILTER_DESTRUCTOR(filter) \
39 void PHP_HTTP_FILTER_DTOR(filter)(php_stream_filter *this TSRMLS_DC)
40 #define PHP_HTTP_FILTER_FUNC(filter) \
41 http_filter_ ##filter
42 #define PHP_HTTP_FILTER_FUNCTION(filter) \
43 php_stream_filter_status_t PHP_HTTP_FILTER_FUNC(filter)(PHP_HTTP_FILTER_PARAMS)
44 #define PHP_HTTP_FILTER_BUFFER(filter) \
45 http_filter_ ##filter## _buffer
46
47 #define PHP_HTTP_FILTER_IS_CLOSING(stream, flags) \
48 ( (flags & PSFS_FLAG_FLUSH_CLOSE) \
49 || php_stream_eof(stream) \
50 || ((stream->ops == &php_stream_temp_ops || stream->ops == &php_stream_memory_ops) && stream->eof) \
51 )
52
53 #define NEW_BUCKET(data, length) \
54 { \
55 char *__data; \
56 php_stream_bucket *__buck; \
57 \
58 __data = pemalloc(length, this->is_persistent); \
59 if (!__data) { \
60 return PSFS_ERR_FATAL; \
61 } \
62 memcpy(__data, data, length); \
63 \
64 __buck = php_stream_bucket_new(stream, __data, length, 1, this->is_persistent TSRMLS_CC); \
65 if (!__buck) { \
66 pefree(__data, this->is_persistent); \
67 return PSFS_ERR_FATAL; \
68 } \
69 \
70 php_stream_bucket_append(buckets_out, __buck TSRMLS_CC); \
71 }
72
73 typedef struct _http_chunked_decode_filter_buffer_t {
74 php_http_buffer_t buffer;
75 ulong hexlen;
76 } PHP_HTTP_FILTER_BUFFER(chunked_decode);
77
78 typedef php_http_encoding_stream_t PHP_HTTP_FILTER_BUFFER(zlib);
79
80 static PHP_HTTP_FILTER_FUNCTION(chunked_decode)
81 {
82 int out_avail = 0;
83 php_stream_bucket *ptr, *nxt;
84 PHP_HTTP_FILTER_BUFFER(chunked_decode) *buffer = (PHP_HTTP_FILTER_BUFFER(chunked_decode) *) (this->abstract);
85
86 if (bytes_consumed) {
87 *bytes_consumed = 0;
88 }
89
90 /* fetch available bucket data */
91 for (ptr = buckets_in->head; ptr; ptr = nxt) {
92 if (bytes_consumed) {
93 *bytes_consumed += ptr->buflen;
94 }
95
96 if (PHP_HTTP_BUFFER_NOMEM == php_http_buffer_append(PHP_HTTP_BUFFER(buffer), ptr->buf, ptr->buflen)) {
97 return PSFS_ERR_FATAL;
98 }
99
100 nxt = ptr->next;
101 php_stream_bucket_unlink(ptr TSRMLS_CC);
102 php_stream_bucket_delref(ptr TSRMLS_CC);
103 }
104
105 if (!php_http_buffer_fix(PHP_HTTP_BUFFER(buffer))) {
106 return PSFS_ERR_FATAL;
107 }
108
109 /* we have data in our buffer */
110 while (PHP_HTTP_BUFFER(buffer)->used) {
111
112 /* we already know the size of the chunk and are waiting for data */
113 if (buffer->hexlen) {
114
115 /* not enough data buffered */
116 if (PHP_HTTP_BUFFER(buffer)->used < buffer->hexlen) {
117
118 /* flush anyway? */
119 if (flags & PSFS_FLAG_FLUSH_INC) {
120
121 /* flush all data (should only be chunk data) */
122 out_avail = 1;
123 NEW_BUCKET(PHP_HTTP_BUFFER(buffer)->data, PHP_HTTP_BUFFER(buffer)->used);
124
125 /* waiting for less data now */
126 buffer->hexlen -= PHP_HTTP_BUFFER(buffer)->used;
127 /* no more buffered data */
128 php_http_buffer_reset(PHP_HTTP_BUFFER(buffer));
129 /* break */
130 }
131
132 /* we have too less data and don't need to flush */
133 else {
134 break;
135 }
136 }
137
138 /* we seem to have all data of the chunk */
139 else {
140 out_avail = 1;
141 NEW_BUCKET(PHP_HTTP_BUFFER(buffer)->data, buffer->hexlen);
142
143 /* remove outgoing data from the buffer */
144 php_http_buffer_cut(PHP_HTTP_BUFFER(buffer), 0, buffer->hexlen);
145 /* reset hexlen */
146 buffer->hexlen = 0;
147 /* continue */
148 }
149 }
150
151 /* we don't know the length of the chunk yet */
152 else {
153 size_t off = 0;
154
155 /* ignore preceeding CRLFs (too loose?) */
156 while (off < PHP_HTTP_BUFFER(buffer)->used && (
157 PHP_HTTP_BUFFER(buffer)->data[off] == '\n' ||
158 PHP_HTTP_BUFFER(buffer)->data[off] == '\r')) {
159 ++off;
160 }
161 if (off) {
162 php_http_buffer_cut(PHP_HTTP_BUFFER(buffer), 0, off);
163 }
164
165 /* still data there? */
166 if (PHP_HTTP_BUFFER(buffer)->used) {
167 int eollen;
168 const char *eolstr;
169
170 /* we need eol, so we can be sure we have all hex digits */
171 php_http_buffer_fix(PHP_HTTP_BUFFER(buffer));
172 if ((eolstr = php_http_locate_bin_eol(PHP_HTTP_BUFFER(buffer)->data, PHP_HTTP_BUFFER(buffer)->used, &eollen))) {
173 char *stop = NULL;
174
175 /* read in chunk size */
176 buffer->hexlen = strtoul(PHP_HTTP_BUFFER(buffer)->data, &stop, 16);
177
178 /* if strtoul() stops at the beginning of the buffered data
179 there's something oddly wrong, i.e. bad input */
180 if (stop == PHP_HTTP_BUFFER(buffer)->data) {
181 return PSFS_ERR_FATAL;
182 }
183
184 /* cut out <chunk size hex><chunk extension><eol> */
185 php_http_buffer_cut(PHP_HTTP_BUFFER(buffer), 0, eolstr + eollen - PHP_HTTP_BUFFER(buffer)->data);
186 /* buffer->hexlen is 0 now or contains the size of the next chunk */
187 if (!buffer->hexlen) {
188 php_stream_notify_info(stream->context, PHP_STREAM_NOTIFY_COMPLETED, NULL, 0);
189 break;
190 }
191 /* continue */
192 } else {
193 /* we have not enough data buffered to read in chunk size */
194 break;
195 }
196 }
197 /* break */
198 }
199 }
200
201 /* flush before close, but only if we are already waiting for more data */
202 if (PHP_HTTP_FILTER_IS_CLOSING(stream, flags) && buffer->hexlen && PHP_HTTP_BUFFER(buffer)->used) {
203 out_avail = 1;
204 NEW_BUCKET(PHP_HTTP_BUFFER(buffer)->data, PHP_HTTP_BUFFER(buffer)->used);
205 php_http_buffer_reset(PHP_HTTP_BUFFER(buffer));
206 buffer->hexlen = 0;
207 }
208
209 return out_avail ? PSFS_PASS_ON : PSFS_FEED_ME;
210 }
211
212 static PHP_HTTP_FILTER_DESTRUCTOR(chunked_decode)
213 {
214 PHP_HTTP_FILTER_BUFFER(chunked_decode) *b = (PHP_HTTP_FILTER_BUFFER(chunked_decode) *) (this->abstract);
215
216 php_http_buffer_dtor(PHP_HTTP_BUFFER(b));
217 pefree(b, this->is_persistent);
218 }
219
220 static PHP_HTTP_FILTER_FUNCTION(chunked_encode)
221 {
222 php_http_buffer_t buf;
223 php_stream_bucket *ptr, *nxt;
224
225 if (bytes_consumed) {
226 *bytes_consumed = 0;
227 }
228
229 /* new data available? */
230 php_http_buffer_init(&buf);
231
232 /* fetch available bucket data */
233 for (ptr = buckets_in->head; ptr; ptr = nxt) {
234 if (bytes_consumed) {
235 *bytes_consumed += ptr->buflen;
236 }
237 #if DBG_FILTER
238 fprintf(stderr, "update: chunked (-> %zu) (w: %zu, r: %zu)\n", ptr->buflen, stream->writepos, stream->readpos);
239 #endif
240
241 nxt = ptr->next;
242 php_stream_bucket_unlink(ptr TSRMLS_CC);
243 php_http_buffer_appendf(&buf, "%lx" PHP_HTTP_CRLF, (long unsigned int) ptr->buflen);
244 php_http_buffer_append(&buf, ptr->buf, ptr->buflen);
245 php_http_buffer_appends(&buf, PHP_HTTP_CRLF);
246
247 /* pass through */
248 NEW_BUCKET(buf.data, buf.used);
249 /* reset */
250 php_http_buffer_reset(&buf);
251 php_stream_bucket_delref(ptr TSRMLS_CC);
252 }
253
254 /* free buffer */
255 php_http_buffer_dtor(&buf);
256
257 /* terminate with "0" */
258 if (PHP_HTTP_FILTER_IS_CLOSING(stream, flags)) {
259 #if DBG_FILTER
260 fprintf(stderr, "finish: chunked\n");
261 #endif
262
263 NEW_BUCKET("0" PHP_HTTP_CRLF PHP_HTTP_CRLF, lenof("0" PHP_HTTP_CRLF PHP_HTTP_CRLF));
264 }
265
266 return PSFS_PASS_ON;
267 }
268
269 static PHP_HTTP_FILTER_OPS(chunked_decode) = {
270 PHP_HTTP_FILTER_FUNC(chunked_decode),
271 PHP_HTTP_FILTER_DTOR(chunked_decode),
272 "http.chunked_decode"
273 };
274
275 static PHP_HTTP_FILTER_OPS(chunked_encode) = {
276 PHP_HTTP_FILTER_FUNC(chunked_encode),
277 NULL,
278 "http.chunked_encode"
279 };
280
281 static PHP_HTTP_FILTER_FUNCTION(zlib)
282 {
283 php_stream_bucket *ptr, *nxt;
284 PHP_HTTP_FILTER_BUFFER(zlib) *buffer = (PHP_HTTP_FILTER_BUFFER(zlib) *) this->abstract;
285
286 if (bytes_consumed) {
287 *bytes_consumed = 0;
288 }
289
290 /* fetch available bucket data */
291 for (ptr = buckets_in->head; ptr; ptr = nxt) {
292 char *encoded = NULL;
293 size_t encoded_len = 0;
294
295 if (bytes_consumed) {
296 *bytes_consumed += ptr->buflen;
297 }
298
299 #if DBG_FILTER
300 fprintf(stderr, "bucket: b=%p p=%p p=%p\n", ptr->brigade, ptr->prev, ptr->next);
301 #endif
302
303 nxt = ptr->next;
304 php_stream_bucket_unlink(ptr TSRMLS_CC);
305 php_http_encoding_stream_update(buffer, ptr->buf, ptr->buflen, &encoded, &encoded_len);
306
307 #if DBG_FILTER
308 fprintf(stderr, "update: deflate (-> %zu) (w: %zu, r: %zu)\n", encoded_len, stream->writepos, stream->readpos);
309 #endif
310
311 if (encoded) {
312 if (encoded_len) {
313 NEW_BUCKET(encoded, encoded_len);
314 }
315 efree(encoded);
316 }
317 php_stream_bucket_delref(ptr TSRMLS_CC);
318 }
319
320 /* flush & close */
321 if (flags & PSFS_FLAG_FLUSH_INC) {
322 char *encoded = NULL;
323 size_t encoded_len = 0;
324
325 php_http_encoding_stream_flush(buffer, &encoded, &encoded_len);
326
327 #if DBG_FILTER
328 fprintf(stderr, "flush: deflate (-> %zu)\n", encoded_len);
329 #endif
330
331 if (encoded) {
332 if (encoded_len) {
333 NEW_BUCKET(encoded, encoded_len);
334 }
335 efree(encoded);
336 }
337 }
338
339 if (PHP_HTTP_FILTER_IS_CLOSING(stream, flags)) {
340 char *encoded = NULL;
341 size_t encoded_len = 0;
342
343 php_http_encoding_stream_finish(buffer, &encoded, &encoded_len);
344
345 #if DBG_FILTER
346 fprintf(stderr, "finish: deflate (-> %zu)\n", encoded_len);
347 #endif
348
349 if (encoded) {
350 if (encoded_len) {
351 NEW_BUCKET(encoded, encoded_len);
352 }
353 efree(encoded);
354 }
355 }
356
357 return PSFS_PASS_ON;
358 }
359 static PHP_HTTP_FILTER_DESTRUCTOR(zlib)
360 {
361 PHP_HTTP_FILTER_BUFFER(zlib) *buffer = (PHP_HTTP_FILTER_BUFFER(zlib) *) this->abstract;
362 php_http_encoding_stream_free(&buffer);
363 }
364
365 static PHP_HTTP_FILTER_OPS(deflate) = {
366 PHP_HTTP_FILTER_FUNC(zlib),
367 PHP_HTTP_FILTER_DTOR(zlib),
368 "http.deflate"
369 };
370
371 static PHP_HTTP_FILTER_OPS(inflate) = {
372 PHP_HTTP_FILTER_FUNC(zlib),
373 PHP_HTTP_FILTER_DTOR(zlib),
374 "http.inflate"
375 };
376
377 static php_stream_filter *http_filter_create(const char *name, zval *params, int p TSRMLS_DC)
378 {
379 zval **tmp = &params;
380 php_stream_filter *f = NULL;
381 int flags = p ? PHP_HTTP_ENCODING_STREAM_PERSISTENT : 0;
382
383 if (params) {
384 switch (Z_TYPE_P(params)) {
385 case IS_ARRAY:
386 case IS_OBJECT:
387 if (SUCCESS != zend_hash_find(HASH_OF(params), "flags", sizeof("flags"), (void *) &tmp)) {
388 break;
389 }
390 /* no break */
391 default:
392 {
393 zval *num = php_http_ztyp(IS_LONG, *tmp);
394
395 flags |= (Z_LVAL_P(num) & 0x0fffffff);
396 zval_ptr_dtor(&num);
397
398 }
399 break;
400 }
401 }
402
403 if (!strcasecmp(name, "http.chunked_decode")) {
404 PHP_HTTP_FILTER_BUFFER(chunked_decode) *b = NULL;
405
406 if ((b = pecalloc(1, sizeof(PHP_HTTP_FILTER_BUFFER(chunked_decode)), p))) {
407 php_http_buffer_init_ex(PHP_HTTP_BUFFER(b), 4096, p ? PHP_HTTP_BUFFER_INIT_PERSISTENT : 0);
408 if (!(f = php_stream_filter_alloc(&PHP_HTTP_FILTER_OP(chunked_decode), b, p))) {
409 pefree(b, p);
410 }
411 }
412 } else
413
414 if (!strcasecmp(name, "http.chunked_encode")) {
415 f = php_stream_filter_alloc(&PHP_HTTP_FILTER_OP(chunked_encode), NULL, p);
416 } else
417
418 if (!strcasecmp(name, "http.inflate")) {
419 PHP_HTTP_FILTER_BUFFER(zlib) *b = NULL;
420
421 if ((b = php_http_encoding_stream_init(NULL, php_http_encoding_stream_get_inflate_ops(), flags TSRMLS_CC))) {
422 if (!(f = php_stream_filter_alloc(&PHP_HTTP_FILTER_OP(inflate), b, p))) {
423 php_http_encoding_stream_free(&b);
424 }
425 }
426 } else
427
428 if (!strcasecmp(name, "http.deflate")) {
429 PHP_HTTP_FILTER_BUFFER(zlib) *b = NULL;
430
431 if ((b = php_http_encoding_stream_init(NULL, php_http_encoding_stream_get_deflate_ops(), flags TSRMLS_CC))) {
432 if (!(f = php_stream_filter_alloc(&PHP_HTTP_FILTER_OP(deflate), b, p))) {
433 php_http_encoding_stream_free(&b);
434 }
435 }
436 }
437
438 return f;
439 }
440
441 php_stream_filter_factory php_http_filter_factory = {
442 http_filter_create
443 };
444
445
446 /*
447 * Local variables:
448 * tab-width: 4
449 * c-basic-offset: 4
450 * End:
451 * vim600: noet sw=4 ts=4 fdm=marker
452 * vim<600: noet sw=4 ts=4
453 */