Config.w32: link to libssl.lib & libcrypto.lib
[m6w6/ext-http] / src / 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);
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, \
31 int flags
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)
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); \
65 if (!__buck) { \
66 pefree(__data, this->is_persistent); \
67 return PSFS_ERR_FATAL; \
68 } \
69 \
70 php_stream_bucket_append(buckets_out, __buck); \
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(stream);
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 = Z_PTR(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);
102 php_stream_bucket_delref(ptr);
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(PHP_STREAM_CONTEXT(stream), 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 = Z_PTR(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);
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);
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_FUNCTION(stream)
270 {
271 php_stream_bucket *ptr, *nxt;
272 PHP_HTTP_FILTER_BUFFER(stream) *buffer = Z_PTR(this->abstract);
273
274 if (bytes_consumed) {
275 *bytes_consumed = 0;
276 }
277
278 /* fetch available bucket data */
279 for (ptr = buckets_in->head; ptr; ptr = nxt) {
280 char *encoded = NULL;
281 size_t encoded_len = 0;
282
283 if (bytes_consumed) {
284 *bytes_consumed += ptr->buflen;
285 }
286
287 #if DBG_FILTER
288 fprintf(stderr, "bucket: b=%p p=%p p=%p\n", ptr->brigade, ptr->prev, ptr->next);
289 #endif
290
291 nxt = ptr->next;
292 php_stream_bucket_unlink(ptr);
293 if (SUCCESS != php_http_encoding_stream_update(buffer, ptr->buf, ptr->buflen, &encoded, &encoded_len)) {
294 return PSFS_ERR_FATAL;
295 }
296
297 #if DBG_FILTER
298 fprintf(stderr, "update: compress (-> %zu) (w: %zu, r: %zu)\n", encoded_len, stream->writepos, stream->readpos);
299 #endif
300
301 if (encoded) {
302 if (encoded_len) {
303 NEW_BUCKET(encoded, encoded_len);
304 }
305 efree(encoded);
306 }
307 php_stream_bucket_delref(ptr);
308 }
309
310 /* flush & close */
311 if (flags & PSFS_FLAG_FLUSH_INC) {
312 char *encoded = NULL;
313 size_t encoded_len = 0;
314
315 if (SUCCESS != php_http_encoding_stream_flush(buffer, &encoded, &encoded_len)) {
316 return PSFS_ERR_FATAL;
317 }
318
319 #if DBG_FILTER
320 fprintf(stderr, "flush: compress (-> %zu)\n", encoded_len);
321 #endif
322
323 if (encoded) {
324 if (encoded_len) {
325 NEW_BUCKET(encoded, encoded_len);
326 }
327 efree(encoded);
328 }
329 }
330
331 if (PHP_HTTP_FILTER_IS_CLOSING(stream, flags)) {
332 char *encoded = NULL;
333 size_t encoded_len = 0;
334
335 if (SUCCESS != php_http_encoding_stream_finish(buffer, &encoded, &encoded_len)) {
336 return PSFS_ERR_FATAL;
337 }
338
339 #if DBG_FILTER
340 fprintf(stderr, "finish: compress (-> %zu)\n", encoded_len);
341 #endif
342
343 if (encoded) {
344 if (encoded_len) {
345 NEW_BUCKET(encoded, encoded_len);
346 }
347 efree(encoded);
348 }
349 }
350
351 return PSFS_PASS_ON;
352 }
353
354 static PHP_HTTP_FILTER_DESTRUCTOR(stream)
355 {
356 PHP_HTTP_FILTER_BUFFER(stream) *buffer = Z_PTR(this->abstract);
357 php_http_encoding_stream_free(&buffer);
358 }
359
360 static PHP_HTTP_FILTER_OPS(chunked_decode) = {
361 PHP_HTTP_FILTER_FUNC(chunked_decode),
362 PHP_HTTP_FILTER_DTOR(chunked_decode),
363 "http.chunked_decode"
364 };
365
366 static PHP_HTTP_FILTER_OPS(chunked_encode) = {
367 PHP_HTTP_FILTER_FUNC(chunked_encode),
368 NULL,
369 "http.chunked_encode"
370 };
371
372 static PHP_HTTP_FILTER_OPS(deflate) = {
373 PHP_HTTP_FILTER_FUNC(stream),
374 PHP_HTTP_FILTER_DTOR(stream),
375 "http.deflate"
376 };
377
378 static PHP_HTTP_FILTER_OPS(inflate) = {
379 PHP_HTTP_FILTER_FUNC(stream),
380 PHP_HTTP_FILTER_DTOR(stream),
381 "http.inflate"
382 };
383
384 #if PHP_HTTP_HAVE_LIBBROTLI
385 static PHP_HTTP_FILTER_OPS(brotli_encode) = {
386 PHP_HTTP_FILTER_FUNC(stream),
387 PHP_HTTP_FILTER_DTOR(stream),
388 "http.brotli_encode"
389 };
390
391 static PHP_HTTP_FILTER_OPS(brotli_decode) = {
392 PHP_HTTP_FILTER_FUNC(stream),
393 PHP_HTTP_FILTER_DTOR(stream),
394 "http.brotli_decode"
395 };
396 #endif
397
398 #if PHP_VERSION_ID >= 70200
399 static php_stream_filter *http_filter_create(const char *name, zval *params, uint8_t p)
400 #else
401 static php_stream_filter *http_filter_create(const char *name, zval *params, int p)
402 #endif
403 {
404 zval *tmp = params;
405 php_stream_filter *f = NULL;
406 int flags = p ? PHP_HTTP_ENCODING_STREAM_PERSISTENT : 0;
407
408 if (params) {
409 switch (Z_TYPE_P(params)) {
410 case IS_ARRAY:
411 case IS_OBJECT:
412 if (!(tmp = zend_hash_str_find_ind(HASH_OF(params), ZEND_STRL("flags")))) {
413 break;
414 }
415 /* no break */
416 default:
417 flags |= zval_get_long(tmp) & 0x0fffffff;
418 break;
419 }
420 }
421
422 if (!strcasecmp(name, "http.chunked_decode")) {
423 PHP_HTTP_FILTER_BUFFER(chunked_decode) *b = NULL;
424
425 if ((b = pecalloc(1, sizeof(PHP_HTTP_FILTER_BUFFER(chunked_decode)), p))) {
426 php_http_buffer_init_ex(PHP_HTTP_BUFFER(b), 4096, p ? PHP_HTTP_BUFFER_INIT_PERSISTENT : 0);
427 if (!(f = php_stream_filter_alloc(&PHP_HTTP_FILTER_OP(chunked_decode), b, p))) {
428 pefree(b, p);
429 }
430 }
431 } else
432
433 if (!strcasecmp(name, "http.chunked_encode")) {
434 f = php_stream_filter_alloc(&PHP_HTTP_FILTER_OP(chunked_encode), NULL, p);
435 } else
436
437 if (!strcasecmp(name, "http.inflate")) {
438 PHP_HTTP_FILTER_BUFFER(stream) *b = NULL;
439
440 if ((b = php_http_encoding_stream_init(NULL, php_http_encoding_stream_get_inflate_ops(), flags))) {
441 if (!(f = php_stream_filter_alloc(&PHP_HTTP_FILTER_OP(inflate), b, p))) {
442 php_http_encoding_stream_free(&b);
443 }
444 }
445 } else
446
447 if (!strcasecmp(name, "http.deflate")) {
448 PHP_HTTP_FILTER_BUFFER(stream) *b = NULL;
449
450 if ((b = php_http_encoding_stream_init(NULL, php_http_encoding_stream_get_deflate_ops(), flags))) {
451 if (!(f = php_stream_filter_alloc(&PHP_HTTP_FILTER_OP(deflate), b, p))) {
452 php_http_encoding_stream_free(&b);
453 }
454 }
455 #if PHP_HTTP_HAVE_LIBBROTLI
456 } else
457
458 if (!strcasecmp(name, "http.brotli_encode")) {
459 PHP_HTTP_FILTER_BUFFER(stream) *b = NULL;
460
461 if ((b = php_http_encoding_stream_init(NULL, php_http_encoding_stream_get_enbrotli_ops(), flags))) {
462 if (!(f = php_stream_filter_alloc(&PHP_HTTP_FILTER_OP(brotli_encode), b, p))) {
463 php_http_encoding_stream_free(&b);
464 }
465 }
466 } else
467
468 if (!strcasecmp(name, "http.brotli_decode")) {
469 PHP_HTTP_FILTER_BUFFER(stream) *b = NULL;
470
471 if ((b = php_http_encoding_stream_init(NULL, php_http_encoding_stream_get_debrotli_ops(), flags))) {
472 if (!(f = php_stream_filter_alloc(&PHP_HTTP_FILTER_OP(brotli_decode), b, p))) {
473 php_http_encoding_stream_free(&b);
474 }
475 }
476 #endif
477 }
478
479 return f;
480 }
481
482 php_stream_filter_factory php_http_filter_factory = {
483 http_filter_create
484 };
485
486
487 /*
488 * Local variables:
489 * tab-width: 4
490 * c-basic-offset: 4
491 * End:
492 * vim600: noet sw=4 ts=4 fdm=marker
493 * vim<600: noet sw=4 ts=4
494 */