/*
 * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

/*
 * replace.c: Example NSAPI filter to substitute one string for another.
 *
 * The example-replace filter defined in this file examines outgoing content
 * and substitutes one string for another. It is intended to demonstrate how
 * to intercept and modify outgoing data.
 *
 * Usage:
 *
 *     At the end of magnus.conf:
 *
 *         Init fn="load-modules"
 *              shlib="<path>/replace.<ext>"
 *              NativeThread="no"
 *
 *     Inside an object in obj.conf:
 *
 *         Output fn="insert-filter"
 *                type="text/*"
 *                filter="example-replace"
 *                from="iPlanet"
 *                to="Sun ONE"
 *
 */

#ifdef XP_WIN32
#define NSAPI_PUBLIC __declspec(dllexport)
#else /* !XP_WIN32 */
#define NSAPI_PUBLIC
#endif /* !XP_WIN32 */

/* 
 * nsapi.h declares the NSAPI interface.
 */
#include "nsapi.h"


/* -------------------------- ExampleReplaceData -------------------------- */

/*
 * ExampleReplaceData will be used to store information between filter method
 * invocations. Each instance of the example-replace filter will have its own
 * ExampleReplaceData object.
 */

typedef struct ExampleReplaceData ExampleReplaceData;

struct ExampleReplaceData {
    char *from;   /* the string to replace */
    int fromlen;  /* length of "from" */
    char *to;     /* the string to replace "from" with */
    int tolen;    /* length of "to" */
    int matched;  /* number of "from" chars matched */
};


/* ------------------------ example_replace_insert ------------------------ */

/*
 * example_replace_insert implements the example-replace filter's insert
 * method. The insert filter method is called before the server adds the
 * filter to the filter stack.
 */

#ifdef __cplusplus
extern "C"
#endif
int example_replace_insert(FilterLayer *layer, pblock *pb)
{
    const char *from;
    const char *to;
    ExampleReplaceData *data;

    /*
     * Look for the string to replace, "from", and the string to replace it
     * with, "to". Both values are required.
     */
    from = pblock_findval("from", pb);
    to = pblock_findval("to", pb);
    if (from == NULL || to == NULL || strlen(from) < 1) {
        log_error(LOG_MISCONFIG, "example-replace-insert",
                  layer->context->sn, layer->context->rq,
                  "missing parameter (need from and to)");
        return REQ_ABORTED; /* error preparing for insertion */
    }

    /*
     * Allocate an ExampleReplaceData object that will store configuration and
     * state information.
     */
    data = (ExampleReplaceData *)MALLOC(sizeof(ExampleReplaceData));
    if (data == NULL)
        return REQ_ABORTED; /* error preparing for insertion */

    /* Initialize the ExampleReplaceData */
    data->from = STRDUP(from);
    data->fromlen = strlen(from);
    data->to = STRDUP(to);
    data->tolen = strlen(to);
    data->matched = 0;

    /* Check for out of memory errors */
    if (data->from == NULL || data->to == NULL) {
        FREE(data->from);
        FREE(data->to);
        FREE(data);
        return REQ_ABORTED; /* error preparing for insertion */
    }

    /*
     * Store a pointer to the ExampleReplaceData object in the FilterLayer.
     * This information can then be accessed from other filter methods.
     */
    layer->context->data = data;

    /* Remove the Content-length: header if we might change the body length */
    if (data->tolen != data->fromlen) {
        pb_param *pp;
        pp = pblock_remove("content-length", layer->context->rq->srvhdrs);
        if (pp)
            param_free(pp);
    }

    return REQ_PROCEED; /* insert filter */
}


/* ------------------------ example_replace_remove ------------------------ */

/*
 * example_replace_remove implements the example-replace filter's remove
 * method. The remove filter method is called before the server removes the
 * filter from the filter stack.
 */

#ifdef __cplusplus
extern "C"
#endif
void example_replace_remove(FilterLayer *layer)
{
    ExampleReplaceData *data;

    /* Access the ExampleReplaceData we allocated in example_replace_insert */
    data = (ExampleReplaceData *)layer->context->data;

    /* Send any partial "from" match */
    if (data->matched > 0)
        net_write(layer->lower, data->from, data->matched);

    /* Destroy the ExampleReplaceData object */
    FREE(data->from);
    FREE(data->to);
    FREE(data);
}


/* ------------------------ example_replace_write ------------------------- */

/*
 * example_replace_write implements the example-replace filter's write method.
 * The write filter method is called when there is data to be sent to the
 * client.
 */

#ifdef __cplusplus
extern "C"
#endif
int example_replace_write(FilterLayer *layer, const void *buf, int amount)
{
    ExampleReplaceData *data;
    const char *buffer;
    int consumed;
    int i;
    int unsent;
    int rv;

    /* Access the ExampleReplaceData we allocated in example_replace_insert */
    data = (ExampleReplaceData *)layer->context->data;

    /* Check for "from" matches in the caller's buffer */
    buffer = (const char *)buf;
    consumed = 0;
    for (i = 0; i < amount; i++) {
        /* Check whether this character matches */
        if (buffer[i] == data->from[data->matched]) {
            /* Matched a(nother) character */
            data->matched++;

            /* If we've now matched all of "from"... */
            if (data->matched == data->fromlen) {
                /* Send any data that preceded the match */
                unsent = i + 1 - consumed - data->matched;
                if (unsent > 0) {
                    rv = net_write(layer->lower, &buffer[consumed], unsent);
                    if (rv != unsent)
                        return IO_ERROR;
                }

                /* Send "to" in place of "from" */
                rv = net_write(layer->lower, data->to, data->tolen);
                if (rv != data->tolen)
                    return IO_ERROR;

                /* We've handled up to and including buffer[i] */
                consumed = i + 1;

                /* Start looking for the next "from" match from scratch */
                data->matched = 0;
            }

        } else if (data->matched > 0) {
            /* This match didn't pan out, we need to backtrack */
            int j;
            int backtrack = data->matched;
            data->matched = 0;

            /* Check for other potential "from" matches preceding buffer[i] */
            for (j = 1; j < backtrack; j++) {
                /* Check whether this character matches */
                if (data->from[j] == data->from[data->matched]) {
                    /* Matched a(nother) character */
                    data->matched++;

                } else if (data->matched > 0) {
                    /* This match didn't pan out, we need to backtrack */
                    j -= data->matched;
                    data->matched = 0;
                }
            }

            /* If the failed (partial) match begins before the buffer... */
            unsent = backtrack - data->matched;
            if (unsent > i) {
                /* Send the failed (partial) match */
                rv = net_write(layer->lower, data->from, unsent);
                if (rv != unsent)
                    return IO_ERROR;

                /* We've handled up to, but not including, buffer[i] */
                consumed = i;
            }

            /* We're not done with buffer[i] yet */
            i--;
        }
    }

    /* Send any data we know won't be part of a future "from" match */
    unsent = amount - consumed - data->matched;
    if (unsent > 0) {
        rv = net_write(layer->lower, &buffer[consumed], unsent);
        if (rv != unsent)
            return IO_ERROR;
    }

    return amount;
}


/* -------------------------- nsapi_module_init --------------------------- */

/*
 * This is the module initialization entry point for this NSAPI plugin. The
 * server calls this entry point in response to the Init fn="load-modules"
 * line in magnus.conf.
 */

NSAPI_PUBLIC nsapi_module_init(pblock *pb, Session *sn, Request *rq)
{
    FilterMethods methods = FILTER_METHODS_INITIALIZER;
    const Filter *filter;

    /*
     * Create the example-replace filter. The example-replace filter has order
     * FILTER_CONTENT_TRANSLATION, meaning it transforms content (entity body
     * data) from one form to another. The example-replace filter implements
     * the write filter method, meaning it is interested in outgoing data.
     */
    methods.insert = &example_replace_insert;
    methods.remove = &example_replace_remove;
    methods.write = &example_replace_write;
    filter = filter_create("example-replace",
                           FILTER_CONTENT_TRANSLATION,
                           &methods);
    if (filter == NULL) {
        pblock_nvinsert("error", system_errmsg(), pb);
        return REQ_ABORTED; /* error initializing plugin */
    }

    return REQ_PROCEED; /* success */
}
