/*
   +----------------------------------------------------------------------+
   | Copyright © The PHP Group and Contributors.                          |
   +----------------------------------------------------------------------+
   | This source file is subject to the Modified BSD License that is      |
   | bundled with this package in the file LICENSE, and is available      |
   | through the World Wide Web at <https://www.php.net/license/>.        |
   |                                                                      |
   | SPDX-License-Identifier: BSD-3-Clause                                |
   +----------------------------------------------------------------------+
   | Author: Wez Furlong <wez@thebrainroom.com>                           |
   | With suggestions from:                                               |
   |      Moriyoshi Koizumi <moriyoshi@at.wakwak.com>                     |
   |      Sara Golemon      <pollita@php.net>                             |
   +----------------------------------------------------------------------+
 */

/* The filter API works on the principle of "Bucket-Brigades".  This is
 * partially inspired by the Apache 2 method of doing things, although
 * it is intentionally a light-weight implementation.
 *
 * Each stream can have a chain of filters for reading and another for writing.
 *
 * When data is written to the stream, it is placed into a bucket and placed at
 * the start of the input brigade.
 *
 * The first filter in the chain is invoked on the brigade and (depending on
 * it's return value), the next filter is invoked and so on.
 * */

#define PHP_STREAM_FILTER_READ	0x0001
#define PHP_STREAM_FILTER_WRITE	0x0002
#define PHP_STREAM_FILTER_ALL	(PHP_STREAM_FILTER_READ | PHP_STREAM_FILTER_WRITE)

typedef struct _php_stream_bucket			php_stream_bucket;
typedef struct _php_stream_bucket_brigade	php_stream_bucket_brigade;

struct _php_stream_bucket {
	php_stream_bucket *next, *prev;
	php_stream_bucket_brigade *brigade;

	char *buf;
	size_t buflen;
	/* if non-zero, buf should be pefreed when the bucket is destroyed */
	bool own_buf;
	bool is_persistent;

	/* destroy this struct when refcount falls to zero */
	int refcount;
};

struct _php_stream_bucket_brigade {
	php_stream_bucket *head, *tail;
};

typedef enum {
	PSFS_ERR_FATAL,	/* error in data stream */
	PSFS_FEED_ME,	/* filter needs more data; stop processing chain until more is available */
	PSFS_PASS_ON	/* filter generated output buckets; pass them on to next in chain */
} php_stream_filter_status_t;

typedef enum {
	PSFS_SEEKABLE_NEVER, /* seeking filter never possible */
	PSFS_SEEKABLE_START, /* seeking possible only to start (position 0) */
	PSFS_SEEKABLE_CHECK, /* seeking possible but it is always checked if callback function set */
	PSFS_SEEKABLE_ALWAYS, /* seeking is always possible */
} php_stream_filter_seekable_t;

/* Buckets API. */
BEGIN_EXTERN_C()
PHPAPI php_stream_bucket *php_stream_bucket_new(const php_stream *stream, char *buf, size_t buflen, uint8_t own_buf, uint8_t buf_persistent);
PHPAPI int php_stream_bucket_split(php_stream_bucket *in, php_stream_bucket **left, php_stream_bucket **right, size_t length);
PHPAPI void php_stream_bucket_delref(php_stream_bucket *bucket);
#define php_stream_bucket_addref(bucket)	(bucket)->refcount++
PHPAPI void php_stream_bucket_prepend(php_stream_bucket_brigade *brigade, php_stream_bucket *bucket);
PHPAPI void php_stream_bucket_append(php_stream_bucket_brigade *brigade, php_stream_bucket *bucket);
PHPAPI void php_stream_bucket_unlink(php_stream_bucket *bucket);
PHPAPI php_stream_bucket *php_stream_bucket_make_writeable(php_stream_bucket *bucket);
END_EXTERN_C()

#define PSFS_FLAG_NORMAL		0	/* regular read/write */
#define PSFS_FLAG_FLUSH_INC		1	/* an incremental flush */
#define PSFS_FLAG_FLUSH_CLOSE	2	/* final flush prior to closing */

typedef struct _php_stream_filter_ops {

	php_stream_filter_status_t (*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
			);

	zend_result (*seek)(
			php_stream *stream,
			php_stream_filter *thisfilter,
			zend_off_t offset,
			int whence
	);

	void (*dtor)(php_stream_filter *thisfilter);

	const char *label;

} php_stream_filter_ops;

typedef struct _php_stream_filter_chain {
	php_stream_filter *head, *tail;
	zend_long num_filters;

	/* Owning stream */
	php_stream *stream;
} php_stream_filter_chain;

struct _php_stream_filter {
	const php_stream_filter_ops *fops;
	zval abstract; /* for use by filter implementation */
	php_stream_filter *next;
	php_stream_filter *prev;
	php_stream_filter_seekable_t read_seekable;
	php_stream_filter_seekable_t write_seekable;
	bool is_persistent;

	/* link into stream and chain */
	php_stream_filter_chain *chain;

	/* buffered buckets */
	php_stream_bucket_brigade buffer;

	/* filters are auto_registered when they're applied */
	zend_resource *res;
};

/* stack filter onto a stream */
BEGIN_EXTERN_C()
PHPAPI void php_stream_filter_prepend(php_stream_filter_chain *chain, php_stream_filter *filter);
PHPAPI void php_stream_filter_prepend_ex(php_stream_filter_chain *chain, php_stream_filter *filter);
PHPAPI void php_stream_filter_append(php_stream_filter_chain *chain, php_stream_filter *filter);
PHPAPI zend_result php_stream_filter_append_ex(php_stream_filter_chain *chain, php_stream_filter *filter);
PHPAPI zend_result php_stream_filter_flush(php_stream_filter *filter, bool finish);
PHPAPI php_stream_filter *php_stream_filter_remove(php_stream_filter *filter, bool call_dtor);
PHPAPI void php_stream_filter_free(php_stream_filter *filter);
PHPAPI php_stream_filter *_php_stream_filter_alloc(const php_stream_filter_ops *fops,
		void *abstract, bool persistent, php_stream_filter_seekable_t read_seekable,
		php_stream_filter_seekable_t write_seekable STREAMS_DC);
PHPAPI zend_result php_stream_filter_parse_write_seek_mode(const zval *filterparams,
		php_stream_filter_seekable_t *write_seekable);
PHPAPI int php_stream_filter_get_chain_type(php_stream *stream, php_stream_filter *filter);

END_EXTERN_C()
#define php_stream_filter_alloc(fops, thisptr, persistent, rseek, wseek) \
		_php_stream_filter_alloc((fops), (thisptr), (persistent), (rseek), (wseek) STREAMS_CC)
#define php_stream_filter_alloc_rel(fops, thisptr, persistent, rseek, wseek) \
	_php_stream_filter_alloc((fops), (thisptr), (persistent), (rseek), (wseek) STREAMS_REL_CC)

#define php_stream_is_filtered(stream)	((stream)->readfilters.head || (stream)->writefilters.head)

typedef struct _php_stream_filter_factory {
	php_stream_filter *(*create_filter)(const char *filtername, zval *filterparams, bool persistent);
} php_stream_filter_factory;

BEGIN_EXTERN_C()
PHPAPI zend_result php_stream_filter_register_factory(const char *filterpattern, const php_stream_filter_factory *factory);
PHPAPI zend_result php_stream_filter_unregister_factory(const char *filterpattern);
PHPAPI zend_result php_stream_filter_register_factory_volatile(zend_string *filterpattern, const php_stream_filter_factory *factory);
PHPAPI php_stream_filter *php_stream_filter_create(const char *filtername, zval *filterparams, bool persistent);
END_EXTERN_C()
