static PHP_HTTP_FILTER_FUNCTION(chunked_encode) { php_http_buffer_t buf; php_stream_bucket *ptr, *nxt; if (bytes_consumed) { *bytes_consumed = 0; } /* new data available? */ php_http_buffer_init(&buf); /* fetch available bucket data */ for (ptr = buckets_in->head; ptr; ptr = nxt) { if (bytes_consumed) { *bytes_consumed += ptr->buflen; } #if DBG_FILTER fprintf(stderr, "update: chunked (-> %zu) (w: %zu, r: %zu)\n", ptr->buflen, stream->writepos, stream->readpos); #endif nxt = ptr->next; php_stream_bucket_unlink(ptr); php_http_buffer_appendf(&buf, "%lx" PHP_HTTP_CRLF, (long unsigned int) ptr->buflen); php_http_buffer_append(&buf, ptr->buf, ptr->buflen); php_http_buffer_appends(&buf, PHP_HTTP_CRLF); /* pass through */ NEW_BUCKET(buf.data, buf.used); /* reset */ php_http_buffer_reset(&buf); php_stream_bucket_delref(ptr); } /* free buffer */ php_http_buffer_dtor(&buf); /* terminate with "0" */ if (PHP_HTTP_FILTER_IS_CLOSING(stream, flags)) { #if DBG_FILTER fprintf(stderr, "finish: chunked\n"); #endif NEW_BUCKET("0" PHP_HTTP_CRLF PHP_HTTP_CRLF, lenof("0" PHP_HTTP_CRLF PHP_HTTP_CRLF)); } return PSFS_PASS_ON; }
/* Given a bucket, returns a version of that bucket with a writeable buffer. * If the original bucket has a refcount of 1 and owns its buffer, then it * is returned unchanged. * Otherwise, a copy of the buffer is made. * In both cases, the original bucket is unlinked from its brigade. * If a copy is made, the original bucket is delref'd. * */ PHPAPI php_stream_bucket *php_stream_bucket_make_writeable(php_stream_bucket *bucket) { php_stream_bucket *retval; php_stream_bucket_unlink(bucket); if (bucket->refcount == 1 && bucket->own_buf) { return bucket; } retval = (php_stream_bucket*)pemalloc(sizeof(php_stream_bucket), bucket->is_persistent); memcpy(retval, bucket, sizeof(*retval)); retval->buf = pemalloc(retval->buflen, retval->is_persistent); memcpy(retval->buf, bucket->buf, retval->buflen); retval->refcount = 1; retval->own_buf = 1; php_stream_bucket_delref(bucket); return retval; }
static php_stream_filter_status_t php_mcrypt_filter( php_stream *stream, php_stream_filter *thisfilter, php_stream_bucket_brigade *buckets_in, php_stream_bucket_brigade *buckets_out, size_t *bytes_consumed, int flags TSRMLS_DC) { php_mcrypt_filter_data *data; php_stream_bucket *bucket; size_t consumed = 0; php_stream_filter_status_t exit_status = PSFS_FEED_ME; if (!thisfilter || !thisfilter->abstract) { /* Should never happen */ return PSFS_ERR_FATAL; } data = (php_mcrypt_filter_data *)(thisfilter->abstract); while(buckets_in->head) { bucket = buckets_in->head; consumed += bucket->buflen; if (data->blocksize) { /* Blockmode cipher */ char *outchunk; int chunklen = bucket->buflen + data->block_used, n; php_stream_bucket *newbucket; outchunk = pemalloc(chunklen, data->persistent); if (data->block_used) { memcpy(outchunk, data->block_buffer, data->block_used); } memcpy(outchunk + data->block_used, bucket->buf, bucket->buflen); for(n=0; (n + data->blocksize) <= chunklen; n += data->blocksize) { if (data->encrypt) { mcrypt_generic(data->module, outchunk + n, data->blocksize); } else { mdecrypt_generic(data->module, outchunk + n, data->blocksize); } } data->block_used = chunklen - n; memcpy(data->block_buffer, outchunk + n, data->block_used); newbucket = php_stream_bucket_new(stream, outchunk, n, 1, data->persistent TSRMLS_CC); php_stream_bucket_append(buckets_out, newbucket TSRMLS_CC); exit_status = PSFS_PASS_ON; php_stream_bucket_unlink(bucket TSRMLS_CC); php_stream_bucket_delref(bucket TSRMLS_CC); } else { /* Stream cipher */ php_stream_bucket_make_writeable(bucket TSRMLS_CC); if (data->encrypt) { mcrypt_generic(data->module, bucket->buf, bucket->buflen); } else { mdecrypt_generic(data->module, bucket->buf, bucket->buflen); } php_stream_bucket_append(buckets_out, bucket TSRMLS_CC); exit_status = PSFS_PASS_ON; } } if ((flags & PSFS_FLAG_FLUSH_CLOSE) && data->blocksize && data->block_used) { php_stream_bucket *newbucket; memset(data->block_buffer + data->block_used, 0, data->blocksize - data->block_used); if (data->encrypt) { mcrypt_generic(data->module, data->block_buffer, data->blocksize); } else { mdecrypt_generic(data->module, data->block_buffer, data->blocksize); } newbucket = php_stream_bucket_new(stream, data->block_buffer, data->blocksize, 0, data->persistent TSRMLS_CC); php_stream_bucket_append(buckets_out, newbucket TSRMLS_CC); exit_status = PSFS_PASS_ON; } if (bytes_consumed) { *bytes_consumed = consumed; } return exit_status; }
php_stream_filter_status_t userfilter_filter( php_stream *stream, php_stream_filter *thisfilter, php_stream_bucket_brigade *buckets_in, php_stream_bucket_brigade *buckets_out, size_t *bytes_consumed, int flags ) { int ret = PSFS_ERR_FATAL; zval *obj = &thisfilter->abstract; zval func_name; zval retval; zval args[4]; zval zpropname; int call_result; /* the userfilter object probably doesn't exist anymore */ if (CG(unclean_shutdown)) { return ret; } if (!zend_hash_str_exists(Z_OBJPROP_P(obj), "stream", sizeof("stream")-1)) { zval tmp; /* Give the userfilter class a hook back to the stream */ php_stream_to_zval(stream, &tmp); zval_copy_ctor(&tmp); add_property_zval(obj, "stream", &tmp); /* add_property_zval increments the refcount which is unwanted here */ zval_ptr_dtor(&tmp); } ZVAL_STRINGL(&func_name, "filter", sizeof("filter")-1); /* Setup calling arguments */ ZVAL_RES(&args[0], zend_register_resource(buckets_in, le_bucket_brigade)); ZVAL_RES(&args[1], zend_register_resource(buckets_out, le_bucket_brigade)); if (bytes_consumed) { ZVAL_LONG(&args[2], *bytes_consumed); } else { ZVAL_NULL(&args[2]); } ZVAL_BOOL(&args[3], flags & PSFS_FLAG_FLUSH_CLOSE); call_result = call_user_function_ex(NULL, obj, &func_name, &retval, 4, args, 0, NULL); zval_ptr_dtor(&func_name); if (call_result == SUCCESS && Z_TYPE(retval) != IS_UNDEF) { convert_to_long(&retval); ret = (int)Z_LVAL(retval); } else if (call_result == FAILURE) { php_error_docref(NULL, E_WARNING, "failed to call filter function"); } if (bytes_consumed) { *bytes_consumed = zval_get_long(&args[2]); } if (buckets_in->head) { php_stream_bucket *bucket = buckets_in->head; php_error_docref(NULL, E_WARNING, "Unprocessed filter buckets remaining on input brigade"); while ((bucket = buckets_in->head)) { /* Remove unconsumed buckets from the brigade */ php_stream_bucket_unlink(bucket); php_stream_bucket_delref(bucket); } } if (ret != PSFS_PASS_ON) { php_stream_bucket *bucket = buckets_out->head; while (bucket != NULL) { php_stream_bucket_unlink(bucket); php_stream_bucket_delref(bucket); bucket = buckets_out->head; } } /* filter resources are cleaned up by the stream destructor, * keeping a reference to the stream resource here would prevent it * from being destroyed properly */ ZVAL_STRINGL(&zpropname, "stream", sizeof("stream")-1); Z_OBJ_HANDLER_P(obj, unset_property)(obj, &zpropname, NULL); zval_ptr_dtor(&zpropname); zval_ptr_dtor(&args[3]); zval_ptr_dtor(&args[2]); zval_ptr_dtor(&args[1]); zval_ptr_dtor(&args[0]); return ret; }
static PHP_HTTP_FILTER_FUNCTION(chunked_decode) { int out_avail = 0; php_stream_bucket *ptr, *nxt; PHP_HTTP_FILTER_BUFFER(chunked_decode) *buffer = Z_PTR(this->abstract); if (bytes_consumed) { *bytes_consumed = 0; } /* fetch available bucket data */ for (ptr = buckets_in->head; ptr; ptr = nxt) { if (bytes_consumed) { *bytes_consumed += ptr->buflen; } if (PHP_HTTP_BUFFER_NOMEM == php_http_buffer_append(PHP_HTTP_BUFFER(buffer), ptr->buf, ptr->buflen)) { return PSFS_ERR_FATAL; } nxt = ptr->next; php_stream_bucket_unlink(ptr); php_stream_bucket_delref(ptr); } if (!php_http_buffer_fix(PHP_HTTP_BUFFER(buffer))) { return PSFS_ERR_FATAL; } /* we have data in our buffer */ while (PHP_HTTP_BUFFER(buffer)->used) { /* we already know the size of the chunk and are waiting for data */ if (buffer->hexlen) { /* not enough data buffered */ if (PHP_HTTP_BUFFER(buffer)->used < buffer->hexlen) { /* flush anyway? */ if (flags & PSFS_FLAG_FLUSH_INC) { /* flush all data (should only be chunk data) */ out_avail = 1; NEW_BUCKET(PHP_HTTP_BUFFER(buffer)->data, PHP_HTTP_BUFFER(buffer)->used); /* waiting for less data now */ buffer->hexlen -= PHP_HTTP_BUFFER(buffer)->used; /* no more buffered data */ php_http_buffer_reset(PHP_HTTP_BUFFER(buffer)); /* break */ } /* we have too less data and don't need to flush */ else { break; } } /* we seem to have all data of the chunk */ else { out_avail = 1; NEW_BUCKET(PHP_HTTP_BUFFER(buffer)->data, buffer->hexlen); /* remove outgoing data from the buffer */ php_http_buffer_cut(PHP_HTTP_BUFFER(buffer), 0, buffer->hexlen); /* reset hexlen */ buffer->hexlen = 0; /* continue */ } } /* we don't know the length of the chunk yet */ else { size_t off = 0; /* ignore preceeding CRLFs (too loose?) */ while (off < PHP_HTTP_BUFFER(buffer)->used && ( PHP_HTTP_BUFFER(buffer)->data[off] == '\n' || PHP_HTTP_BUFFER(buffer)->data[off] == '\r')) { ++off; } if (off) { php_http_buffer_cut(PHP_HTTP_BUFFER(buffer), 0, off); } /* still data there? */ if (PHP_HTTP_BUFFER(buffer)->used) { int eollen; const char *eolstr; /* we need eol, so we can be sure we have all hex digits */ php_http_buffer_fix(PHP_HTTP_BUFFER(buffer)); if ((eolstr = php_http_locate_bin_eol(PHP_HTTP_BUFFER(buffer)->data, PHP_HTTP_BUFFER(buffer)->used, &eollen))) { char *stop = NULL; /* read in chunk size */ buffer->hexlen = strtoul(PHP_HTTP_BUFFER(buffer)->data, &stop, 16); /* if strtoul() stops at the beginning of the buffered data there's something oddly wrong, i.e. bad input */ if (stop == PHP_HTTP_BUFFER(buffer)->data) { return PSFS_ERR_FATAL; } /* cut out <chunk size hex><chunk extension><eol> */ php_http_buffer_cut(PHP_HTTP_BUFFER(buffer), 0, eolstr + eollen - PHP_HTTP_BUFFER(buffer)->data); /* buffer->hexlen is 0 now or contains the size of the next chunk */ if (!buffer->hexlen) { php_stream_notify_info(PHP_STREAM_CONTEXT(stream), PHP_STREAM_NOTIFY_COMPLETED, NULL, 0); break; } /* continue */ } else { /* we have not enough data buffered to read in chunk size */ break; } } /* break */ } } /* flush before close, but only if we are already waiting for more data */ if (PHP_HTTP_FILTER_IS_CLOSING(stream, flags) && buffer->hexlen && PHP_HTTP_BUFFER(buffer)->used) { out_avail = 1; NEW_BUCKET(PHP_HTTP_BUFFER(buffer)->data, PHP_HTTP_BUFFER(buffer)->used); php_http_buffer_reset(PHP_HTTP_BUFFER(buffer)); buffer->hexlen = 0; } return out_avail ? PSFS_PASS_ON : PSFS_FEED_ME; }
static PHP_HTTP_FILTER_FUNCTION(stream) { php_stream_bucket *ptr, *nxt; PHP_HTTP_FILTER_BUFFER(stream) *buffer = Z_PTR(this->abstract); if (bytes_consumed) { *bytes_consumed = 0; } /* fetch available bucket data */ for (ptr = buckets_in->head; ptr; ptr = nxt) { char *encoded = NULL; size_t encoded_len = 0; if (bytes_consumed) { *bytes_consumed += ptr->buflen; } #if DBG_FILTER fprintf(stderr, "bucket: b=%p p=%p p=%p\n", ptr->brigade, ptr->prev, ptr->next); #endif nxt = ptr->next; php_stream_bucket_unlink(ptr); if (SUCCESS != php_http_encoding_stream_update(buffer, ptr->buf, ptr->buflen, &encoded, &encoded_len)) { return PSFS_ERR_FATAL; } #if DBG_FILTER fprintf(stderr, "update: compress (-> %zu) (w: %zu, r: %zu)\n", encoded_len, stream->writepos, stream->readpos); #endif if (encoded) { if (encoded_len) { NEW_BUCKET(encoded, encoded_len); } efree(encoded); } php_stream_bucket_delref(ptr); } /* flush & close */ if (flags & PSFS_FLAG_FLUSH_INC) { char *encoded = NULL; size_t encoded_len = 0; if (SUCCESS != php_http_encoding_stream_flush(buffer, &encoded, &encoded_len)) { return PSFS_ERR_FATAL; } #if DBG_FILTER fprintf(stderr, "flush: compress (-> %zu)\n", encoded_len); #endif if (encoded) { if (encoded_len) { NEW_BUCKET(encoded, encoded_len); } efree(encoded); } } if (PHP_HTTP_FILTER_IS_CLOSING(stream, flags)) { char *encoded = NULL; size_t encoded_len = 0; if (SUCCESS != php_http_encoding_stream_finish(buffer, &encoded, &encoded_len)) { return PSFS_ERR_FATAL; } #if DBG_FILTER fprintf(stderr, "finish: compress (-> %zu)\n", encoded_len); #endif if (encoded) { if (encoded_len) { NEW_BUCKET(encoded, encoded_len); } efree(encoded); } } return PSFS_PASS_ON; }
PHPAPI int _php_stream_filter_flush(php_stream_filter *filter, int finish) { php_stream_bucket_brigade brig_a = { NULL, NULL }, brig_b = { NULL, NULL }, *inp = &brig_a, *outp = &brig_b, *brig_temp; php_stream_bucket *bucket; php_stream_filter_chain *chain; php_stream_filter *current; php_stream *stream; size_t flushed_size = 0; long flags = (finish ? PSFS_FLAG_FLUSH_CLOSE : PSFS_FLAG_FLUSH_INC); if (!filter->chain || !filter->chain->stream) { /* Filter is not attached to a chain, or chain is somehow not part of a stream */ return FAILURE; } chain = filter->chain; stream = chain->stream; for(current = filter; current; current = current->next) { php_stream_filter_status_t status; status = filter->fops->filter(stream, current, inp, outp, NULL, flags); if (status == PSFS_FEED_ME) { /* We've flushed the data far enough */ return SUCCESS; } if (status == PSFS_ERR_FATAL) { return FAILURE; } /* Otherwise we have data available to PASS_ON Swap the brigades and continue */ brig_temp = inp; inp = outp; outp = brig_temp; outp->head = NULL; outp->tail = NULL; flags = PSFS_FLAG_NORMAL; } /* Last filter returned data via PSFS_PASS_ON Do something with it */ for(bucket = inp->head; bucket; bucket = bucket->next) { flushed_size += bucket->buflen; } if (flushed_size == 0) { /* Unlikely, but possible */ return SUCCESS; } if (chain == &(stream->readfilters)) { /* Dump any newly flushed data to the read buffer */ if (stream->readpos > 0) { /* Back the buffer up */ memcpy(stream->readbuf, stream->readbuf + stream->readpos, stream->writepos - stream->readpos); stream->readpos = 0; stream->writepos -= stream->readpos; } if (flushed_size > (stream->readbuflen - stream->writepos)) { /* Grow the buffer */ stream->readbuf = perealloc(stream->readbuf, stream->writepos + flushed_size + stream->chunk_size, stream->is_persistent); } while ((bucket = inp->head)) { memcpy(stream->readbuf + stream->writepos, bucket->buf, bucket->buflen); stream->writepos += bucket->buflen; php_stream_bucket_unlink(bucket); php_stream_bucket_delref(bucket); } } else if (chain == &(stream->writefilters)) { /* Send flushed data to the stream */ while ((bucket = inp->head)) { stream->ops->write(stream, bucket->buf, bucket->buflen); php_stream_bucket_unlink(bucket); php_stream_bucket_delref(bucket); } } return SUCCESS; }
PHPAPI int php_stream_filter_append_ex(php_stream_filter_chain *chain, php_stream_filter *filter) { php_stream *stream = chain->stream; filter->prev = chain->tail; filter->next = NULL; if (chain->tail) { chain->tail->next = filter; } else { chain->head = filter; } chain->tail = filter; filter->chain = chain; if (&(stream->readfilters) == chain && (stream->writepos - stream->readpos) > 0) { /* Let's going ahead and wind anything in the buffer through this filter */ php_stream_bucket_brigade brig_in = { NULL, NULL }, brig_out = { NULL, NULL }; php_stream_bucket_brigade *brig_inp = &brig_in, *brig_outp = &brig_out; php_stream_filter_status_t status; php_stream_bucket *bucket; size_t consumed = 0; bucket = php_stream_bucket_new(stream, (char*) stream->readbuf + stream->readpos, stream->writepos - stream->readpos, 0, 0); php_stream_bucket_append(brig_inp, bucket); status = filter->fops->filter(stream, filter, brig_inp, brig_outp, &consumed, PSFS_FLAG_NORMAL); if (stream->readpos + consumed > (uint)stream->writepos) { /* No behaving filter should cause this. */ status = PSFS_ERR_FATAL; } switch (status) { case PSFS_ERR_FATAL: while (brig_in.head) { bucket = brig_in.head; php_stream_bucket_unlink(bucket); php_stream_bucket_delref(bucket); } while (brig_out.head) { bucket = brig_out.head; php_stream_bucket_unlink(bucket); php_stream_bucket_delref(bucket); } php_error_docref(NULL, E_WARNING, "Filter failed to process pre-buffered data"); return FAILURE; case PSFS_FEED_ME: /* We don't actually need data yet, leave this filter in a feed me state until data is needed. Reset stream's internal read buffer since the filter is "holding" it. */ stream->readpos = 0; stream->writepos = 0; break; case PSFS_PASS_ON: /* If any data is consumed, we cannot rely upon the existing read buffer, as the filtered data must replace the existing data, so invalidate the cache */ /* note that changes here should be reflected in main/streams/streams.c::php_stream_fill_read_buffer */ stream->writepos = 0; stream->readpos = 0; while (brig_outp->head) { bucket = brig_outp->head; /* Grow buffer to hold this bucket if need be. TODO: See warning in main/stream/streams.c::php_stream_fill_read_buffer */ if (stream->readbuflen - stream->writepos < bucket->buflen) { stream->readbuflen += bucket->buflen; stream->readbuf = perealloc(stream->readbuf, stream->readbuflen, stream->is_persistent); } memcpy(stream->readbuf + stream->writepos, bucket->buf, bucket->buflen); stream->writepos += bucket->buflen; php_stream_bucket_unlink(bucket); php_stream_bucket_delref(bucket); } break; } } return SUCCESS; }