Line data Source code
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 374 : PHP_MINIT_FUNCTION(http_filter)
20 : {
21 374 : php_stream_filter_register_factory("http.*", &php_http_filter_factory TSRMLS_CC);
22 374 : 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 1 : static PHP_HTTP_FILTER_FUNCTION(chunked_decode)
81 : {
82 1 : int out_avail = 0;
83 : php_stream_bucket *ptr, *nxt;
84 1 : PHP_HTTP_FILTER_BUFFER(chunked_decode) *buffer = (PHP_HTTP_FILTER_BUFFER(chunked_decode) *) (this->abstract);
85 :
86 1 : if (bytes_consumed) {
87 0 : *bytes_consumed = 0;
88 : }
89 :
90 : /* fetch available bucket data */
91 2 : for (ptr = buckets_in->head; ptr; ptr = nxt) {
92 1 : if (bytes_consumed) {
93 0 : *bytes_consumed += ptr->buflen;
94 : }
95 :
96 1 : if (PHP_HTTP_BUFFER_NOMEM == php_http_buffer_append(PHP_HTTP_BUFFER(buffer), ptr->buf, ptr->buflen)) {
97 0 : return PSFS_ERR_FATAL;
98 : }
99 :
100 1 : nxt = ptr->next;
101 1 : php_stream_bucket_unlink(ptr TSRMLS_CC);
102 1 : php_stream_bucket_delref(ptr TSRMLS_CC);
103 : }
104 :
105 1 : if (!php_http_buffer_fix(PHP_HTTP_BUFFER(buffer))) {
106 0 : return PSFS_ERR_FATAL;
107 : }
108 :
109 : /* we have data in our buffer */
110 46 : while (PHP_HTTP_BUFFER(buffer)->used) {
111 :
112 : /* we already know the size of the chunk and are waiting for data */
113 45 : if (buffer->hexlen) {
114 :
115 : /* not enough data buffered */
116 22 : if (PHP_HTTP_BUFFER(buffer)->used < buffer->hexlen) {
117 :
118 : /* flush anyway? */
119 0 : if (flags & PSFS_FLAG_FLUSH_INC) {
120 :
121 : /* flush all data (should only be chunk data) */
122 0 : out_avail = 1;
123 0 : NEW_BUCKET(PHP_HTTP_BUFFER(buffer)->data, PHP_HTTP_BUFFER(buffer)->used);
124 :
125 : /* waiting for less data now */
126 0 : buffer->hexlen -= PHP_HTTP_BUFFER(buffer)->used;
127 : /* no more buffered data */
128 0 : 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 0 : break;
135 : }
136 : }
137 :
138 : /* we seem to have all data of the chunk */
139 : else {
140 22 : out_avail = 1;
141 22 : NEW_BUCKET(PHP_HTTP_BUFFER(buffer)->data, buffer->hexlen);
142 :
143 : /* remove outgoing data from the buffer */
144 22 : php_http_buffer_cut(PHP_HTTP_BUFFER(buffer), 0, buffer->hexlen);
145 : /* reset hexlen */
146 22 : buffer->hexlen = 0;
147 : /* continue */
148 : }
149 : }
150 :
151 : /* we don't know the length of the chunk yet */
152 : else {
153 23 : size_t off = 0;
154 :
155 : /* ignore preceeding CRLFs (too loose?) */
156 157 : while (off < PHP_HTTP_BUFFER(buffer)->used && (
157 112 : PHP_HTTP_BUFFER(buffer)->data[off] == '\n' ||
158 45 : PHP_HTTP_BUFFER(buffer)->data[off] == '\r')) {
159 44 : ++off;
160 : }
161 23 : if (off) {
162 22 : php_http_buffer_cut(PHP_HTTP_BUFFER(buffer), 0, off);
163 : }
164 :
165 : /* still data there? */
166 23 : 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 23 : php_http_buffer_fix(PHP_HTTP_BUFFER(buffer));
172 23 : if ((eolstr = php_http_locate_bin_eol(PHP_HTTP_BUFFER(buffer)->data, PHP_HTTP_BUFFER(buffer)->used, &eollen))) {
173 23 : char *stop = NULL;
174 :
175 : /* read in chunk size */
176 23 : 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 23 : if (stop == PHP_HTTP_BUFFER(buffer)->data) {
181 0 : return PSFS_ERR_FATAL;
182 : }
183 :
184 : /* cut out <chunk size hex><chunk extension><eol> */
185 23 : 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 23 : if (!buffer->hexlen) {
188 1 : php_stream_notify_info(stream->context, PHP_STREAM_NOTIFY_COMPLETED, NULL, 0);
189 1 : break;
190 : }
191 : /* continue */
192 : } else {
193 : /* we have not enough data buffered to read in chunk size */
194 0 : break;
195 : }
196 : }
197 : /* break */
198 : }
199 : }
200 :
201 : /* flush before close, but only if we are already waiting for more data */
202 1 : if (PHP_HTTP_FILTER_IS_CLOSING(stream, flags) && buffer->hexlen && PHP_HTTP_BUFFER(buffer)->used) {
203 0 : out_avail = 1;
204 0 : NEW_BUCKET(PHP_HTTP_BUFFER(buffer)->data, PHP_HTTP_BUFFER(buffer)->used);
205 0 : php_http_buffer_reset(PHP_HTTP_BUFFER(buffer));
206 0 : buffer->hexlen = 0;
207 : }
208 :
209 1 : return out_avail ? PSFS_PASS_ON : PSFS_FEED_ME;
210 : }
211 :
212 1 : static PHP_HTTP_FILTER_DESTRUCTOR(chunked_decode)
213 : {
214 1 : PHP_HTTP_FILTER_BUFFER(chunked_decode) *b = (PHP_HTTP_FILTER_BUFFER(chunked_decode) *) (this->abstract);
215 :
216 1 : php_http_buffer_dtor(PHP_HTTP_BUFFER(b));
217 1 : pefree(b, this->is_persistent);
218 1 : }
219 :
220 3349 : static PHP_HTTP_FILTER_FUNCTION(chunked_encode)
221 : {
222 : php_http_buffer_t buf;
223 : php_stream_bucket *ptr, *nxt;
224 :
225 3349 : if (bytes_consumed) {
226 3339 : *bytes_consumed = 0;
227 : }
228 :
229 : /* new data available? */
230 3349 : php_http_buffer_init(&buf);
231 :
232 : /* fetch available bucket data */
233 6655 : for (ptr = buckets_in->head; ptr; ptr = nxt) {
234 3306 : if (bytes_consumed) {
235 3306 : *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 3306 : nxt = ptr->next;
242 3306 : php_stream_bucket_unlink(ptr TSRMLS_CC);
243 3306 : php_http_buffer_appendf(&buf, "%lx" PHP_HTTP_CRLF, (long unsigned int) ptr->buflen);
244 3306 : php_http_buffer_append(&buf, ptr->buf, ptr->buflen);
245 3306 : php_http_buffer_appends(&buf, PHP_HTTP_CRLF);
246 :
247 : /* pass through */
248 3306 : NEW_BUCKET(buf.data, buf.used);
249 : /* reset */
250 3306 : php_http_buffer_reset(&buf);
251 3306 : php_stream_bucket_delref(ptr TSRMLS_CC);
252 : }
253 :
254 : /* free buffer */
255 3349 : php_http_buffer_dtor(&buf);
256 :
257 : /* terminate with "0" */
258 3349 : if (PHP_HTTP_FILTER_IS_CLOSING(stream, flags)) {
259 : #if DBG_FILTER
260 : fprintf(stderr, "finish: chunked\n");
261 : #endif
262 :
263 11 : NEW_BUCKET("0" PHP_HTTP_CRLF PHP_HTTP_CRLF, lenof("0" PHP_HTTP_CRLF PHP_HTTP_CRLF));
264 : }
265 :
266 3349 : 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 46 : static PHP_HTTP_FILTER_FUNCTION(zlib)
282 : {
283 : php_stream_bucket *ptr, *nxt;
284 46 : PHP_HTTP_FILTER_BUFFER(zlib) *buffer = (PHP_HTTP_FILTER_BUFFER(zlib) *) this->abstract;
285 :
286 46 : if (bytes_consumed) {
287 45 : *bytes_consumed = 0;
288 : }
289 :
290 : /* fetch available bucket data */
291 138 : for (ptr = buckets_in->head; ptr; ptr = nxt) {
292 23 : char *encoded = NULL;
293 23 : size_t encoded_len = 0;
294 :
295 23 : if (bytes_consumed) {
296 22 : *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 23 : nxt = ptr->next;
304 23 : php_stream_bucket_unlink(ptr TSRMLS_CC);
305 23 : 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 23 : if (encoded) {
312 23 : if (encoded_len) {
313 2 : NEW_BUCKET(encoded, encoded_len);
314 : }
315 23 : efree(encoded);
316 : }
317 23 : php_stream_bucket_delref(ptr TSRMLS_CC);
318 : }
319 :
320 : /* flush & close */
321 46 : if (flags & PSFS_FLAG_FLUSH_INC) {
322 22 : char *encoded = NULL;
323 22 : size_t encoded_len = 0;
324 :
325 22 : 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 22 : if (encoded) {
332 22 : if (encoded_len) {
333 22 : NEW_BUCKET(encoded, encoded_len);
334 : }
335 22 : efree(encoded);
336 : }
337 : }
338 :
339 46 : if (PHP_HTTP_FILTER_IS_CLOSING(stream, flags)) {
340 2 : char *encoded = NULL;
341 2 : size_t encoded_len = 0;
342 :
343 2 : 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 2 : if (encoded) {
350 1 : if (encoded_len) {
351 1 : NEW_BUCKET(encoded, encoded_len);
352 : }
353 1 : efree(encoded);
354 : }
355 : }
356 :
357 46 : return PSFS_PASS_ON;
358 : }
359 2 : static PHP_HTTP_FILTER_DESTRUCTOR(zlib)
360 : {
361 2 : PHP_HTTP_FILTER_BUFFER(zlib) *buffer = (PHP_HTTP_FILTER_BUFFER(zlib) *) this->abstract;
362 2 : php_http_encoding_stream_free(&buffer);
363 2 : }
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 14 : static php_stream_filter *http_filter_create(const char *name, zval *params, int p TSRMLS_DC)
378 : {
379 14 : zval **tmp = ¶ms;
380 14 : php_stream_filter *f = NULL;
381 14 : int flags = p ? PHP_HTTP_ENCODING_STREAM_PERSISTENT : 0;
382 :
383 14 : if (params) {
384 2 : switch (Z_TYPE_P(params)) {
385 : case IS_ARRAY:
386 : case IS_OBJECT:
387 1 : if (SUCCESS != zend_hash_find(HASH_OF(params), "flags", sizeof("flags"), (void *) &tmp)) {
388 1 : break;
389 : }
390 : /* no break */
391 : default:
392 : {
393 1 : zval *num = php_http_ztyp(IS_LONG, *tmp);
394 :
395 1 : flags |= (Z_LVAL_P(num) & 0x0fffffff);
396 1 : zval_ptr_dtor(&num);
397 :
398 : }
399 1 : break;
400 : }
401 : }
402 :
403 14 : if (!strcasecmp(name, "http.chunked_decode")) {
404 1 : PHP_HTTP_FILTER_BUFFER(chunked_decode) *b = NULL;
405 :
406 1 : if ((b = pecalloc(1, sizeof(PHP_HTTP_FILTER_BUFFER(chunked_decode)), p))) {
407 1 : php_http_buffer_init_ex(PHP_HTTP_BUFFER(b), 4096, p ? PHP_HTTP_BUFFER_INIT_PERSISTENT : 0);
408 1 : if (!(f = php_stream_filter_alloc(&PHP_HTTP_FILTER_OP(chunked_decode), b, p))) {
409 0 : pefree(b, p);
410 : }
411 : }
412 : } else
413 :
414 13 : if (!strcasecmp(name, "http.chunked_encode")) {
415 11 : f = php_stream_filter_alloc(&PHP_HTTP_FILTER_OP(chunked_encode), NULL, p);
416 : } else
417 :
418 2 : if (!strcasecmp(name, "http.inflate")) {
419 1 : PHP_HTTP_FILTER_BUFFER(zlib) *b = NULL;
420 :
421 1 : if ((b = php_http_encoding_stream_init(NULL, php_http_encoding_stream_get_inflate_ops(), flags TSRMLS_CC))) {
422 1 : if (!(f = php_stream_filter_alloc(&PHP_HTTP_FILTER_OP(inflate), b, p))) {
423 0 : php_http_encoding_stream_free(&b);
424 : }
425 : }
426 : } else
427 :
428 1 : if (!strcasecmp(name, "http.deflate")) {
429 1 : PHP_HTTP_FILTER_BUFFER(zlib) *b = NULL;
430 :
431 1 : if ((b = php_http_encoding_stream_init(NULL, php_http_encoding_stream_get_deflate_ops(), flags TSRMLS_CC))) {
432 1 : if (!(f = php_stream_filter_alloc(&PHP_HTTP_FILTER_OP(deflate), b, p))) {
433 0 : php_http_encoding_stream_free(&b);
434 : }
435 : }
436 : }
437 :
438 14 : 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 : */
|