/* ====================================================================
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2000-2001 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Apache" and "Apache Software Foundation" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    nor may "Apache" appear in their name, without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */

#define APR_WANT_STRFUNC
#include "apr_want.h"

#include "httpd.h"
#include "http_log.h"
#include "util_filter.h"

/* ### make this visible for direct manipulation?
 * ### use a hash table
 */
static ap_filter_rec_t *registered_output_filters = NULL;
static ap_filter_rec_t *registered_input_filters = NULL;

/* NOTE: Apache's current design doesn't allow a pool to be passed thu,
   so we depend on a global to hold the correct pool
*/
#define FILTER_POOL     apr_global_hook_pool
#include "apr_hooks.h"   /* for apr_global_hook_pool */

/*
** This macro returns true/false if a given filter should be inserted BEFORE
** another filter. This will happen when one of: 1) there isn't another
** filter; 2) that filter has a higher filter type (class); 3) that filter
** corresponds to a different request.
*/
#define INSERT_BEFORE(f, before_this) ((before_this) == NULL \
                           || (before_this)->frec->ftype > (f)->frec->ftype \
                           || (before_this)->r != (f)->r)


static apr_status_t filter_cleanup(void *ctx)
{
    registered_output_filters = NULL;
    registered_input_filters = NULL;
    return APR_SUCCESS;
}

static void register_filter(const char *name,
                            ap_filter_func filter_func,
                            ap_filter_type ftype,
                            ap_filter_rec_t **reg_filter_list)
{
    ap_filter_rec_t *frec = apr_palloc(FILTER_POOL, sizeof(*frec));

    frec->name = name;
    frec->filter_func = filter_func;
    frec->ftype = ftype;

    frec->next = *reg_filter_list;
    *reg_filter_list = frec;

    apr_pool_cleanup_register(FILTER_POOL, NULL, filter_cleanup, apr_pool_cleanup_null);
}

AP_DECLARE(void) ap_register_input_filter(const char *name,
                                          ap_in_filter_func filter_func,
                                          ap_filter_type ftype)
{
    ap_filter_func f;
    f.in_func = filter_func;
    register_filter(name, f, ftype, &registered_input_filters);
}                                                                    

AP_DECLARE(void) ap_register_output_filter(const char *name,
                                           ap_out_filter_func filter_func,
                                           ap_filter_type ftype)
{
    ap_filter_func f;
    f.out_func = filter_func;
    register_filter(name, f, ftype, &registered_output_filters);
}

static ap_filter_t *add_any_filter(const char *name, void *ctx, 
				   request_rec *r, conn_rec *c, 
				   ap_filter_rec_t *frec,
				   ap_filter_t **r_filters,
				   ap_filter_t **c_filters)
{
    for (; frec != NULL; frec = frec->next) {
        if (!strcasecmp(name, frec->name)) {
            apr_pool_t *p = r ? r->pool : c->pool;
            ap_filter_t *f = apr_pcalloc(p, sizeof(*f));
            ap_filter_t **outf = r ? r_filters : c_filters;

            f->frec = frec;
            f->ctx = ctx;
            f->r = r;
            f->c = c;

            if (INSERT_BEFORE(f, *outf)) {
                f->next = *outf;
                *outf = f;
            }
            else {
                ap_filter_t *fscan = *outf;
                while (!INSERT_BEFORE(f, fscan->next))
                    fscan = fscan->next;
                f->next = fscan->next;
                fscan->next = f;
            }

            return f;
        }
    }

    ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, NULL,
                 "an unknown filter was not added: %s", name);
    return NULL;
}

AP_DECLARE(ap_filter_t *) ap_add_input_filter(const char *name, void *ctx,
					      request_rec *r, conn_rec *c)
{
    return add_any_filter(name, ctx, r, c, registered_input_filters,
			  r ? &r->input_filters : NULL, &c->input_filters);
}

AP_DECLARE(ap_filter_t *) ap_add_output_filter(const char *name, void *ctx,
					       request_rec *r, conn_rec *c)
{
    return add_any_filter(name, ctx, r, c, registered_output_filters,
			  r ? &r->output_filters : NULL, &c->output_filters);
}

AP_DECLARE(void) ap_remove_output_filter(ap_filter_t *f)
{
    ap_filter_t *curr;

    curr = f->r ? f->r->output_filters : f->c->output_filters;

    if (curr == f) {
        if (f->r) {
            f->r->output_filters = f->r->output_filters->next;
        }
        else {
            f->c->output_filters = f->c->output_filters->next;
        }
        return;
    }

    while (curr->next != f) {
        curr = curr->next;
        if (curr == NULL) {
            return;
        }
    }
    curr->next = f->next;
}

/* 
 * Read data from the next filter in the filter stack.  Data should be 
 * modified in the bucket brigade that is passed in.  The core allocates the
 * bucket brigade, modules that wish to replace large chunks of data or to
 * save data off to the side should probably create their own temporary
 * brigade especially for that use.
 */
AP_DECLARE(apr_status_t) ap_get_brigade(ap_filter_t *next,
                                        apr_bucket_brigade *bb, 
                                        ap_input_mode_t mode,
                                        apr_size_t *readbytes)
{
    if (next) {
        return next->frec->filter_func.in_func(next, bb, mode, readbytes);
    }
    return AP_NOBODY_READ;
}

/* Pass the buckets to the next filter in the filter stack.  If the
 * current filter is a handler, we should get NULL passed in instead of
 * the current filter.  At that point, we can just call the first filter in
 * the stack, or r->output_filters.
 */
AP_DECLARE(apr_status_t) ap_pass_brigade(ap_filter_t *next, apr_bucket_brigade *bb)
{
    if (next) {
        apr_bucket *e;
        if ((e = APR_BRIGADE_LAST(bb)) && APR_BUCKET_IS_EOS(e) && next->r) {
            /* This is only safe because HTTP_HEADER filter is always in
             * the filter stack.   This ensures that there is ALWAYS a
             * request-based filter that we can attach this to.  If the
             * HTTP_FILTER is removed, and another filter is not put in its
             * place, then handlers like mod_cgi, which attach their own
             * EOS bucket to the brigade will be broken, because we will
             * get two EOS buckets on the same request.
             */
            next->r->eos_sent = 1;
        }
        return next->frec->filter_func.out_func(next, bb);
    }
    return AP_NOBODY_WROTE;
}

AP_DECLARE(apr_status_t) ap_save_brigade(ap_filter_t *f, apr_bucket_brigade **saveto,
                                         apr_bucket_brigade **b)
{
    apr_bucket *e;
    apr_pool_t *p = f->r ? f->r->pool : f->c->pool;
    apr_status_t rv;

    /* If have never stored any data in the filter, then we had better
     * create an empty bucket brigade so that we can concat.
     */
    if (!(*saveto)) {
        *saveto = apr_brigade_create(p);
    }
    
    APR_RING_FOREACH(e, &(*b)->list, apr_bucket, link) {
        rv = apr_bucket_setaside(e);
        if (rv != APR_SUCCESS && rv != APR_ENOTIMPL) {
            return rv;
        }
    }
    APR_BRIGADE_CONCAT(*saveto, *b);
    return APR_SUCCESS;
}

AP_DECLARE_NONSTD(apr_status_t) ap_filter_flush(apr_bucket_brigade *bb, void *ctx)
{
    ap_filter_t *f = ctx;

    return ap_pass_brigade(f, bb);
}

AP_DECLARE(apr_status_t) ap_fflush(ap_filter_t *f, apr_bucket_brigade *bb)
{
    apr_bucket *b;

    b = apr_bucket_flush_create();
    APR_BRIGADE_INSERT_TAIL(bb, b);
    return ap_pass_brigade(f, bb);
}

AP_DECLARE_NONSTD(int) ap_fputstrs(ap_filter_t *f, apr_bucket_brigade *bb, ...)
{
    va_list args;
    int res;

    va_start(args, bb);
    res = apr_brigade_vputstrs(bb, ap_filter_flush, f, args);
    va_end(args);
    return res;
}

AP_DECLARE_NONSTD(int) ap_fprintf(ap_filter_t *f, apr_bucket_brigade *bb, const char *fmt, ...){
    va_list args;
    int res;

    va_start(args, fmt);
    res = apr_brigade_vprintf(bb, ap_filter_flush, f, fmt, args);
    va_end(args);
    return res;
}

