/*
 * $Header: /cvsroot/csrc/modhttp/http.c,v 1.18 2003/12/02 14:49:22 aleigh
 * Exp $
 */

/*
 * Copyright (c) 1994, 2004 Alex Leigh, All Rights Reserved
 * 
 * THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF ALEX LEIGH The copyright
 * notice above does not evidence any actual or intended publication of such
 * source code.
 * 
 * Disclosure, copying, distribution, or transmission of this code without
 * express permission is prohibited.
 */

#include "../continuity/include/csys.h"
#include "../continuity/mecha/dyn.h"
#include "../continuity/mecha/net.h"
#include "../continuity/include/continuity.h"
#include "../continuity/mecha/lst.h"
#include "http.h"

#define EAGAIN_TRYCOUNT 100
#define EAGAIN_WAIT 250

static void httpFat_end(char **sPtr, char **ePtr);
static int httpFparse_multiquery(httpTtrans * t, char *start, char *end,
                                 lstTset * set);

pthread_key_t httpKtrans_key = 0;
int natr_id;
int srvc_id;
int mime_id;
int wlog_id;

static void httpFcleanup(httpTtrans * t)
{
    httpFthread_settrans(NULL);
    t->conn->io_close(t->conn);
    tlsFclean();
    httpFtrans_free(t);
}

/*
 * This function is called by the dispatcher when a new connection is
 * received on the listen socket. This overall controls the flow of the HTTP
 * transaction, from start to finish, and dictates which module classes are
 * going to be called in order to service the transaction.
 */
void httpFhandler(void *p, MECHA_UNUSED_ARGUMENT const lstTset * arg)
{
    netTconn *conn = (netTconn *) p;
    httpTtrans *trans;
    int ret;

    trans = httpFtrans_alloc();

    trans->sd = conn->sd;
    trans->cli_ipv4_addr = conn->cli_ipv4_addr;
    trans->conn = p;

    /*
     * Set the new transaction structure to be associated with the thread, so
     * that httpFthread_gettrans will work later.
     */
    httpFthread_settrans(trans);

    ret = httpFparse(trans);

    if (ret == STATUS_ERROR) {
        /* There was something totally wrong with the query so we are going
         * to return a 400. Since there is no valid information in the transaction
         * the pipelines are not executed, so 400 requests do not log.
         */
        httpFset_status(trans, 400, NULL);
        httpFstart_response(trans);
        httpFerror_page(trans, NULL);
        httpFcleanup(trans);
        return;
    }

    ret = dlFpipeline_exec(trans, natr_id, NULL, NULL);

    if (trans->started == REQ_UNSTARTED) {

        ret = dlFpipeline_exec(trans, mime_id, NULL, NULL);
    }
    if (trans->started == REQ_UNSTARTED) {

        ret =
            dlFpipeline_exec(trans, srvc_id, "type",
                             lstFset_get(trans->res_hdrs, "Content-Type"));
    }
    if (ret != STATUS_EXIT) {
        httpFset_status(trans, 500, NULL);
        httpFstart_response(trans);
        httpFerror_page(trans, NULL);
    }
    if (trans->started == REQ_STARTED)
        httpFsend_response(trans);

    dlFpipeline_exec(trans, wlog_id, NULL, NULL);

    httpFcleanup(trans);

    return;
}

int httpFinit(MECHA_UNUSED_ARGUMENT void *p,
              MECHA_UNUSED_ARGUMENT const lstTset * opts)
{
    if (pthread_key_create(&httpKtrans_key, NULL) != 0) {
        logFmsg(2, "Unable to create httpKtrans_key. [e: %d]",
                errno);
        abort();
    }
    logFmsg(0, "mod/http: Hyper-Text Transfer Protocol Engine");
    logFmsg(0, "Copyright (c) 2002, Alex Leigh");

    natr_id = dlFpipeline_getid("http_natr");
    mime_id = dlFpipeline_getid("http_mime");
    srvc_id = dlFpipeline_getid("http_srvc");
    wlog_id = dlFpipeline_getid("http_log");

    return STATUS_OK;
}

int httpFthread_settrans(httpTtrans * t)
{
    return pthread_setspecific(httpKtrans_key, t);
}

httpTtrans *httpFthread_gettrans(void)
{
    return (httpTtrans *) pthread_getspecific(httpKtrans_key);
}

httpTtrans *httpFtrans_alloc(void)
{
    httpTtrans *t;

    t = (httpTtrans *) malloc(sizeof(httpTtrans));

    t->req_hdrs = lstFset_create("request headers");
    t->res_hdrs = lstFset_create("response headers");
    t->vars = lstFset_create("vars");
    t->response = dynFinit();
    t->reader = netFreader_init();
    t->query = NULL;

    t->res_code = 200;
    t->res_reason = "OK";
    t->started = REQ_UNSTARTED;
    t->client_read = 0;

    memFclear(&t->statb, sizeof(t->statb));

    assert(t->req_hdrs != NULL);
    assert(t->res_hdrs != NULL);
    assert(t->vars != NULL);

    return t;
}

void httpFtrans_free(httpTtrans * t)
{
    assert(t != NULL);
    assert(t->req_hdrs != NULL);
    assert(t->res_hdrs != NULL);
    assert(t->vars != NULL);
    assert(t->conn != NULL);

    netFconn_free(t->conn);

    lstFset_free(t->req_hdrs);
    lstFset_free(t->res_hdrs);
    lstFset_free(t->vars);
    lstFset_free(t->query);

    dynFfree(t->response);
    netFreader_free(t->reader);

    free(t);
}

int httpFerror_page(httpTtrans * t, const char *msg)
{
    char buf[1024];

    assert(t != NULL);

    httpFsend_response(t);

    snprintf(buf, sizeof(buf),
             "ERROR: No functions handled this request.\n\n%s",
             conFget_build());

    httpFwrite(t, buf, strlen(buf));

    if (msg != NULL) {
        httpFwrite(t, msg, strlen(msg));
    }
    return STATUS_PROCEED;
}

void httpFset_status(httpTtrans * t, int status, const char *reason)
{
    assert(t != NULL);

    t->res_code = status;
    if (reason != NULL) {
        t->res_reason = reason;
    }
}

/*
 * This just outputs the headers. It's useful if your module has served the
 * HTTP response line, or you are returning a non HTTP response and you want
 * to send the entities.
 */

int httpFsend_resp_headers(httpTtrans * t)
{
    int i;
    char buf[128];
    size_t length = 0;

    assert(t != NULL);
    assert(t->res_hdrs != NULL);

    for (i = 0; i < t->res_hdrs->size; i++) {
        if (t->res_hdrs->fields[i].name != NULL
            && t->res_hdrs->fields[i].value != NULL) {
            snprintf(buf, sizeof(buf), "%s: %s\r\n",
                     t->res_hdrs->fields[i].name,
                     t->res_hdrs->fields[i].value);
            length +=
                t->res_hdrs->fields[i].n_len +
                t->res_hdrs->fields[i].v_len + 4;

            buf[0] = toupper(buf[0]);

            httpFwrite(t, buf, length);
        }
    }
    return 0;
}

/*
 * this function is used in case we have never called httpFwrite() by the
 * time the request is finished, or if we don't intend to call it.
 */
void httpFsend_response(httpTtrans * t)
{
    netFconn_write(t->conn, t->response->str, t->response->strlen);

    t->started = REQ_HEADERSENT;
}

void httpFstart_response(httpTtrans * t)
{
    int i;

    assert(t != NULL);
    assert(t->res_hdrs != NULL);

    /* we only need to send these once */
    if (t->started == REQ_STARTED || t->started == REQ_HEADERSENT) {
        return;
    }

    t->started = REQ_STARTED;

    switch (t->res_code) {
    case HTTP_OK:
        dynFappend(t->response, "HTTP/1.0 200 OK\r\n", 17);
        break;
    case HTTP_CREATED:
        dynFappend(t->response, "HTTP/1.0 201 CREATED\r\n", 22);
        break;
    case HTTP_ACCEPTED:
        dynFappend(t->response, "HTTP/1.0 202 ACCEPTED\r\n", 23);
        break;
    case HTTP_PARTIAL:
        dynFappend(t->response, "HTTP/1.0 203 PARTIAL\r\n", 22);
        break;
    case HTTP_NORESPONSE:
        dynFappend(t->response, "HTTP/1.0 204 NO RESPONSE\r\n", 26);
        break;
    case HTTP_MOVED:
        dynFappend(t->response, "HTTP/1.0 301 MOVED\r\n", 20);
        break;
    case HTTP_FOUND:
        dynFappend(t->response, "HTTP/1.0 302 FOUND\r\n", 20);
        break;
    case HTTP_NOTMODIFIED:
        dynFappend(t->response, "HTTP/1.0 304 NOT MODIFIED\r\n", 27);
        break;
    case HTTP_BADREQUEST:
        dynFappend(t->response, "HTTP/1.0 400 BAD REQUEST\r\n", 26);
        break;
    case HTTP_UNAUTHORIZED:
        dynFappend(t->response, "HTTP/1.0 401 UNAUTHORIZED\r\n", 27);
        break;
    case HTTP_PAYMENTREQUIRED:
        dynFappend(t->response, "HTTP/1.0 402 PAYMENT REQUIRED\r\n", 31);
        break;
    case HTTP_FORBIDDEN:
        dynFappend(t->response, "HTTP/1.0 403 FORBIDDEN\r\n", 24);
        break;
    case HTTP_NOTFOUND:
        dynFappend(t->response, "HTTP/1.0 404 NOT FOUND\r\n", 24);
        break;
    case HTTP_ERROR:
        dynFappend(t->response, "HTTP/1.0 500 ERROR\r\n", 20);
        break;
    case HTTP_UNIMPLEMENTED:
        dynFappend(t->response, "HTTP/1.0 501 NOT IMPLEMENTED\r\n", 30);
        break;
    case HTTP_TIMEDOUT:
        dynFappend(t->response, "HTTP/1.0 502 TIMED OUT\r\n", 24);
        break;
    default:
        dynFappend(t->response, "HTTP/1.0 500 ERROR\r\n", 20);
        break;
    }

    for (i = 0; i < t->res_hdrs->size; i++) {
        if (t->res_hdrs->fields[i].name != NULL
            && t->res_hdrs->fields[i].value != NULL) {


            /* dynFsappend(t->response, t->res_hdrs->fields[i].name);  */

            dynFappend(t->response, t->res_hdrs->fields[i].name,
                       t->res_hdrs->fields[i].n_len);

            dynFappend(t->response, ": ", 2);

            /* dynFsappend(t->response, t->res_hdrs->fields[i].value);  */

            dynFappend(t->response, t->res_hdrs->fields[i].value,
                       t->res_hdrs->fields[i].v_len);

            dynFappend(t->response, "\r\n", 2);
        }
    }

    dynFappend(t->response, "\r\n", 2);
}

/*
 * httpFparse
 * 
 * Read a HTTP request from the network and fill out the transaction. This
 * function assumes that t->vars are empty and that the network socket
 * descriptor is at the beginning of the input stream.
 * 
 * After this function exits, the transaction will contain the following
 * 
 * t->vars clf-request The complete request line the client provided. t->vars
 * uri The URI of the request, stripped of any query information. t->vars
 * query Anything after the first ? in clf-request. t->vars method The method
 * of the request, as specified by the client. Uppercase. t->vars protocol
 * The protocol the client provided, if any. Uppercase.
 * 
 * STATUS_PROCEED A request was read. STATUS_ERROR The request could not be
 * read. Discard the connection.
 */

int httpFparse(httpTtrans * t)
{
    /* 1024 hardocded later */
    char req_line[1024];
    dynTstring *decoded;
    char *st_p;                 /* strtok pointer */
    char *method_p = NULL, *uri_p = NULL, *proto_p = NULL, *query_p = NULL;
    int method_len, proto_len;
    int ret;
    size_t rqsize;

    ret =
        netFconn_read_line(t->conn, t->reader, req_line, sizeof(req_line));

    if (ret < 1) {
        return STATUS_ERROR;
    }

    if (req_line[0] == '\0')
        return STATUS_ERROR;

    if (ret >= 1024) {
        /*
         * The input buffer was of insufficient size for us to store the entire
         * line.
         */
        logFmsg(1, "Request would not fit in buffer.");
        return STATUS_ERROR;
    }
    rqsize = ret;

    lstFset_badd(t->vars, "clf-request", 11, req_line, ret);

    method_p = (char *) strtok_r(req_line, " ", &st_p);

    /* we don't want to include the " " in this calculation */
    method_len = st_p - method_p - 1;

    uri_p = (char *) strtok_r(NULL, " ", &st_p);
    proto_p = (char *) strtok_r(NULL, " ", &st_p);

    proto_len = (req_line + ret) - proto_p;

    if (method_p == NULL || uri_p == NULL) {
        /* logFmsg (1, "short request"); */
        return STATUS_ERROR;
    }
    lstFset_badd(t->res_hdrs, "Server", 6, "continuity/" BUILD_REV,
                 11 + BUILD_LEV_LEN);

    strFtoupper(method_p);
    lstFset_badd(t->vars, "method", 6, method_p, method_len);

    if (proto_p != NULL) {
        strFtoupper(proto_p);
        lstFset_badd(t->vars, "protocol", 8, proto_p, proto_len);
    }
    /* Check to see if we had GET query parameters */
    st_p = NULL;
    query_p = (char *) strtok_r(uri_p, "?", &st_p);

    decoded = dynFinit();

    /* decode the portion of the uri before the query */
    utlFurl_decode(decoded, query_p);

    lstFset_badd(t->vars, "uri", 3, decoded->str, decoded->strlen);
    lstFset_badd(t->vars, "req_uri", 7, decoded->str, decoded->strlen);

    dynFfree(decoded);

    query_p = (char *) strtok_r(NULL, "?", &st_p);

    if (query_p != NULL) {
        lstFset_badd(t->vars, "query", 5, query_p, strlen(query_p));
    }

    /*
     * 4.2  Message Headers
     * 
     *    HTTP header fields, which include General-Header (Section 4.3),
     *    Request-Header (Section 5.2), Response-Header (Section 6.2), and
     *    Entity-Header (Section 7.1) fields, follow the same generic format as
     *    that given in Section 3.1 of RFC 822 [7]. Each header field consists
     *    of a name followed immediately by a colon (":"), a single space (SP)
     *    character, and the field value. Field names are case-insensitive.
     *    Header fields can be extended over multiple lines by preceding each
     *    extra line with at least one SP or HT, though this is not
     *    recommended.
     */
    while (1) {
        char *name_p;
        char *colon_location;
        int name_len;

        char *value_p;
        int value_len;

        ret =
            netFconn_read_line(t->conn, t->reader, req_line,
                               sizeof(req_line));

        if (ret <= 1)
            break;

        rqsize += ret;

        name_p = req_line;

        colon_location = index(name_p, ':');

        if (colon_location == NULL) {
            continue;
        }

        *colon_location = 0;

        name_len = colon_location - name_p;

        value_p = colon_location + 1;

        /* We should only have to look for one space */
        if (*value_p == ' ') {
            value_p++;
        }

        if (value_p == NULL) {
            continue;
        }

        value_len = (req_line + ret) - value_p;

        strFtolower(name_p);
        lstFset_badd(t->req_hdrs, name_p, name_len, value_p, value_len);
    }

    return STATUS_PROCEED;
}

/*
 * read the conte-type from the request, and break out the boundry
 * information.
 */
int httpFget_boundry(dynTstring * str, httpTtrans * t)
{
    const char *type, *bs, *be;

    type = lstFset_get(t->req_hdrs, "content-type");

    if (type != NULL) {
        if (strstr(type, "multipart/form-data") != NULL) {
            if ((bs = strstr(type, "boundary=")) != NULL) {
                bs += 9;
                be = bs;
                while (*be && !isspace(*be)) {
                    ++be;
                }
                dynFappend(str, "--", 2);
                dynFappend(str, bs, be - bs);
                return 1;
            }
        }
    }
    return 0;
}

/*
 * Trim a string of unescessary crap, from sPtr to ePtr
 */
static void httpFat_end(char **sPtr, char **ePtr)
{
    char *s, *e;

    s = *sPtr;
    e = s;
    while (*e && !isspace(*e)) {
        ++e;
    }
    if (e > s && e[-1] == ';') {
        --e;
    }
    if (*s == '"' && (e - s) > 1 && e[-1] == '"') {
        ++s;
        --e;
    }
    *sPtr = s;
    *ePtr = e;
}

/*
 * Read additional POST data off of the connection. This would normally be
 * called by httpFparse() in response to the method being post. If there is
 * no content-type in the request, then this will simply return -1, otherwise
 * it will try to read and process any available post data from the client. A
 * -1 return indicates a bad response and should probably generate a
 * malformed protocol error.
 * 
 * After the post data is read, it will be t->vars:post_query in it's original
 * encoded form.
 */

int httpFread_post(httpTtrans * t)
{
    dynTstring *query;
    char buf[1024];
    const char *clength;
    ssize_t ret;
    size_t ilength;

    query = dynFinit();

    clength = lstFset_get(t->req_hdrs, "content-length");

    if (clength == NULL)
        return -1;

    ilength = atoi(clength);

    while (ilength > 0) {
        if (ilength > sizeof(buf))
            ret = httpFread(t, buf, sizeof(buf));
        else {
            ret = httpFread(t, buf, ilength);
        }

        if (ret == 0)
            break;

        if (ret < 0) {
            dynFfree(query);
            return -1;
        }
        dynFappend(query, buf, ret);
        ilength -= ret;
    }

    lstFset_badd(t->vars, "post_query", 10, dynFgetstr(query),
                 dynFgetlen(query));
    dynFfree(query);

    return ret;
}

/*
 * Parse a get string query into set
 */
int httpFparse_query(httpTtrans * t, lstTset * set)
{
    char *p, *k, *v;
    char query_buf[1024];
    const char *query;
    dynTstring *kds, *vds;

    query = lstFset_get(t->vars, "post_query");

    if (query == NULL)
        return 0;

    strncpy(query_buf, query, sizeof(query_buf));

    p = query_buf;

    kds = dynFinit();
    vds = dynFinit();

    while (p != NULL) {
        k = p;
        p = strchr(p, '&');
        if (p != NULL) {
            *p++ = '\0';
        }
        v = strchr(k, '=');
        if (v != NULL) {
            *v = '\0';
        }
        utlFurl_decode(kds, k);
        k = kds->str;
        if (v != NULL) {
            utlFurl_decode(vds, v + 1);
            *v = '=';
            v = vds->str;
        }
        lstFset_add(set, k, v);
    }

    dynFfree(kds);
    dynFfree(vds);

    return 1;
}

/*
 * Parse a multiquery between start and end into set
 */
static int httpFparse_multiquery(MECHA_UNUSED_ARGUMENT httpTtrans * t,
                                 char *start, char *end, lstTset * set)
{
    dynTstring *kds, *vds;
    char *s, *e, *ks, *ke, *fs, *fe, save, saveend;

    kds = dynFinit();
    vds = dynFinit();

    /*
     * Trim off the trailing \r\n and null terminate the input.
     */

    if (end > start && end[-1] == '\n')
        --end;
    if (end > start && end[-1] == '\r')
        --end;
    saveend = *end;
    *end = '\0';

    /*
     * Scan non-blank lines for the content-disposition header.
     */

    ks = fs = NULL;
    while ((e = strchr(start, '\n')) != NULL) {
        s = start;
        start = e + 1;
        if (e > s && e[-1] == '\r') {
            --e;
        }
        if (s == e) {
            break;
        }
        save = *e;
        *e = '\0';
        if (strncasecmp(s, "content-disposition", 19) == 0
            && (ks = strstr(s + 19, "name=")) != NULL) {

            /*
             * Save the key name and filename.
             */

            ks += 5;
            httpFat_end(&ks, &ke);
            if ((fs = strstr(s + 19, "filename=")) != NULL) {
                fs += 9;
                httpFat_end(&fs, &fe);
            }
        }
        *e = save;
    }

    /*
     * Save the key/value and file contents if found.
     */

    if (ks != NULL) {
        dynFappend(kds, ks, ke - ks);

        if (fs == NULL) {
            dynFappend(vds, start, end - start);
        } else {
            /* Handle File Content  */
        }

        lstFset_add(set, kds->str, vds->str);
        logFmsg(3, "kv: [%s] [%s]", kds->str, vds->str);
    }
    *end = saveend;
    dynFfree(vds);
    dynFfree(kds);

    return 0;
}

const lstTset *httpFget_query(httpTtrans * t)
{
    const char *q_str;
    dynTstring *tmp_query;

    if (t->query != NULL)
        return t->query;

    q_str = lstFset_get(t->vars, "query");
    if (q_str == NULL)
        return NULL;

    tmp_query = dynFinit();

    utlFurl_decode(tmp_query, q_str);

    t->query = qryFlst_parse(tmp_query->str);

    dynFfree(tmp_query);

    return t->query;
}

const lstTset *httpFget_post_query(httpTtrans * t)
{
    dynTstring *bound;
    char *s, *e;
    const char *form;
    lstTset *set;

    form = lstFset_get(t->vars, "post_query");

    if (form == NULL)
        return NULL;

    set = lstFset_create("post_query");

    bound = dynFinit();

    if (!httpFget_boundry(bound, t)) {
        httpFparse_query(t, set);
    } else {
        s = strstr(form, bound->str);
        logFmsg(3, "caught 0x%x", s);
        while (s != NULL) {
            s += bound->strlen;
            if (*s == '\r')
                ++s;
            if (*s == '\n')
                ++s;
            e = strstr(s, bound->str);
            if (e != NULL) {
                httpFparse_multiquery(t, s, e, set);
            }
            s = e;
        }
    }

    dynFfree(bound);

    return set;
}

void httpFset_state(httpTtrans * t, int state)
{
    t->started = state;
}

int httpFset_nph(httpTtrans * t)
{
    if (t->started == REQ_UNSTARTED) {
        t->started = REQ_HEADERSENT;

        return (1);
    } else {
        return (0);
    }
}

int httpFread(httpTtrans * t, char *buf, size_t len)
{
    return netFconn_read(t->conn, t->reader, buf, len);
}


int httpFwrite(httpTtrans * t, const char *buf, size_t len)
{
    int ret;

    if (t->started == REQ_UNSTARTED) {
        httpFstart_response(t);
    }

    if (t->started == REQ_STARTED) {
        t->started = REQ_HEADERSENT;

        ret = netFconn_write_two(t->conn, t->response->str,
                                 t->response->strlen, buf, len);

        return ret;
    }

    ret = netFconn_write(t->conn, buf, len);

    return ret;
}

const char *httpFquery_val(httpTtrans * t, const char *name)
{
    const char *q_str;
    dynTstring *tmp_query;

    if (t->query != NULL)
        return lstFset_get(t->query, name);

    q_str = lstFset_get(t->vars, "query");
    if (q_str == NULL)
        return NULL;

    tmp_query = dynFinit();

    utlFurl_decode(tmp_query, q_str);

    t->query = qryFlst_parse(tmp_query->str);

    dynFfree(tmp_query);

    if (t->query == NULL)
        return NULL;

    return lstFset_get(t->query, name);
}

int httpFwritef(httpTtrans * t, const char *fmt, ...)
{
    va_list ap;
    int ret1 = 0;
    int ret2 = 0;

    if (t->started == REQ_UNSTARTED) {
        httpFstart_response(t);
    }

    if (t->started == REQ_STARTED) {
        t->started = REQ_HEADERSENT;

        ret1 =
            netFconn_write(t->conn, t->response->str, t->response->strlen);

        if (ret1 < 1) {
            return (ret1);
        }
    }

    va_start(ap, fmt);

    ret2 = _netFconn_writef(t->conn, fmt, &ap);

    va_end(ap);

    if (ret2 < 1) {
        return (ret2);
    }

    return (ret1 + ret2);
}


int httpFdebug_page(httpTtrans * t, MECHA_UNUSED_ARGUMENT char *msg)
{
    int i = 0, ret;
    char buf[256];
    const char *m1 = "<H2>t->vars</H2>\n";
    const char *m2 = "<H2>t->req_hdrs</H2>\n";
    const char *m3 = "<H2>t->res_hdrs</H2>\n";
    dynTstring outbuf;
    char hostname[64];

    lstFset_update(t->res_hdrs, "Content-Type", "text/html");

    gethostname(hostname, 64);

    bzero(&outbuf, sizeof(dynTstring));

    dynFsappend(&outbuf, "<P>");
    dynFsappend(&outbuf, hostname);
    dynFsappend(&outbuf, "<P>");
    dynFsappend(&outbuf, m1);

    while (1) {
        ret = lstFnth_find(t->vars, i);
        if (ret == -1)
            break;
        snprintf(buf, sizeof(buf), "%s : %s<BR>\n",
                 t->vars->fields[i].name, t->vars->fields[i].value);
        dynFappend(&outbuf, buf, strlen(buf));

        i++;
    }

    i = 0;
    dynFsappend(&outbuf, m2);

    while (1) {
        ret = lstFnth_find(t->req_hdrs, i);
        if (ret == -1)
            break;
        snprintf(buf, sizeof(buf), "%s : %s<BR>\n",
                 t->req_hdrs->fields[i].name,
                 t->req_hdrs->fields[i].value);
        dynFappend(&outbuf, buf, strlen(buf));
        i++;
    }

    i = 0;
    dynFsappend(&outbuf, m3);

    while (1) {
        ret = lstFnth_find(t->res_hdrs, i);
        if (ret == -1)
            break;
        snprintf(buf, sizeof(buf), "%s : %s<BR>\n",
                 t->res_hdrs->fields[i].name,
                 t->res_hdrs->fields[i].value);
        dynFappend(&outbuf, buf, strlen(buf));
        i++;
    }

    httpFwrite(t, outbuf.str, strlen(outbuf.str));
    free(outbuf.str);

    return STATUS_EXIT;
}

int httpFredirect(httpTtrans * t, const char *dest)
{
    assert(t != NULL);
    assert(dest != NULL);

    tasFstat_counter_add("http_svc", 0, "httpFredirect", 1);

    lstFset_update(t->res_hdrs, "Location", dest);

    httpFset_status(t, HTTP_FOUND, NULL);
    httpFstart_response(t);

    return STATUS_EXIT;
}

/*
 * httpFmaterial_compare checks to see if a is materially similar to b. 0 =
 * not similar 1 = similar 2 = provisionally similar (POST data)
 */
int httpFmaterial_compare(const httpTtrans * a, const httpTtrans * b)
{
    const lstTfield *as, *bs;

    assert(a != NULL);
    assert(b != NULL);

    as = lstFset_get_field(a->vars, "clf-request", 11);
    bs = lstFset_get_field(b->vars, "clf-request", 11);

    if (as == NULL || bs == NULL) {
        return (0);
    }

    if (lstFfield_value_len(as) != lstFfield_value_len(bs)) {
        return (0);
    }

    if (memcmp(lstFfield_value(as), lstFfield_value(bs),
               lstFfield_value_len(as)) != 0) {
        return (0);
    }

    /*
     * If we got a content-length, there was POST, and we cannot compare the
     * post data
     */

    as = lstFset_get_field(a->req_hdrs, "content-length", 14);

    if (as != NULL) {
        return 2;
    }

    return 1;
}
