/* $Cambridge: hermes/src/prayer/shared/response.c,v 1.8 2009/11/17 11:47:14 dpc22 Exp $ */
/************************************************
 *    Prayer - a Webmail Interface              *
 ************************************************/

/* Copyright (c) University of Cambridge 2000 - 2008 */
/* See the file NOTICE for conditions of use and distribution. */

#include "shared.h"

/* Routines for generating bits of HTTP response */

/* ====================================================================== */

static char *response_version = "Uninitialized";

/* response_record_version() ***********************************************
 *
 * Record version number:
 *   
 **************************************************************************/

void response_record_version(char *version)
{
    response_version = pool_strdup(NIL, version);
}

/* ====================================================================== */

/* response_status_text() ************************************************
 *
 * Convert HTTP status code into human readable text
 ************************************************************************/

static char *response_status_text(unsigned long code)
{
    static struct _HTTPReasons {
        unsigned long status;
        char *reason;
    } *reason, reasons[] = {
        {
        100, "Continue"}
        , {
        101, "Switching Protocols"}
        , {
        200, "OK"}
        , {
        201, "Created"}
        , {
        202, "Accepted"}
        , {
        203, "Non-Authoritative Information"}
        , {
        204, "No Content"}
        , {
        205, "Reset Content"}
        , {
        206, "Partial Content"}
        , {
        300, "Multiple Choices"}
        , {
        301, "Moved Permanently"}
        , {
        302, "Moved Temporarily"}
        , {
        303, "See Other"}
        , {
        304, "Not Modified"}
        , {
        305, "Use Proxy"}
        , {
        400, "Bad Request"}
        , {
        401, "Unauthorized"}
        , {
        402, "Payment Required"}
        , {
        403, "Forbidden"}
        , {
        404, "Not Found"}
        , {
        405, "Method Not Allowed"}
        , {
        406, "Not Acceptable"}
        , {
        407, "Proxy Authentication Required"}
        , {
        408, "Request Timeout"}
        , {
        409, "Conflict"}
        , {
        410, "Gone"}
        , {
        411, "Length Required"}
        , {
        412, "Precondition Failed"}
        , {
        413, "Request Entity Too Large"}
        , {
        414, "Request-URI Too Long"}
        , {
        415, "Unsupported Media Type"}
        , {
        500, "Internal Server Error"}
        , {
        501, "Not Implemented"}
        , {
        502, "Bad Gateway"}
        , {
        503, "Service Unavailable"}
        , {
        504, "Gateway Timeout"}
        , {
        505, "HTTP Version Not Supported"}
        , {
        000, NULL}
    };

    reason = reasons;

    while (reason->status <= code)
        if (reason->status == code)
            return (reason->reason);
        else
            reason++;

    return "No Reason";
}

/* ====================================================================== */

/* response_decode_date() ************************************************
 *
 * Convert HTTP date string into time_t for time comparisons
 ************************************************************************/

static time_t response_decode_date(char *s)
{
    int n = 0;
    struct tm tmvalue, *tm = &tmvalue;
    static char *date_month[12] = {
        "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep",
        "Oct",
        "Nov", "Dec"
    };

    /* Ignore spaces, day name and spaces */
    while ((*s == ' ') || (*s == '\t'))
        s++;

    while ((*s != ' ') && (*s != '\t'))
        s++;

    while ((*s == ' ') || (*s == '\t'))
        s++;

    /* Scanf should be safe as only used to decode numbers, not strings here */
    /* try to recognize the date format */
    if ((sscanf(s, "%*s %d %d:%d:%d %d%*s",
                &tm->tm_mday, &tm->tm_hour, &tm->tm_min,
                &tm->tm_sec, &tm->tm_year) != 5) &&
        (sscanf(s, "%d %n%*s %d %d:%d:%d GMT%*s",
                &tm->tm_mday, &n, &tm->tm_year,
                &tm->tm_hour, &tm->tm_min, &tm->tm_sec) != 5) &&
        (sscanf(s, "%d-%n%*[A-Za-z]-%d %d:%d:%d GMT%*s",
                &tm->tm_mday, &n, &tm->tm_year,
                &tm->tm_hour, &tm->tm_min, &tm->tm_sec) != 5))
        return NIL;

    /* s points now to the month string */
    s += n;
    for (n = 0; n < 12; n++) {
        char *p = date_month[n];

        if (tolower(*p++) == tolower(*s))
            if (*p++ == tolower(s[1]))
                if (*p == tolower(s[2]))
                    break;
    };

    if (n == 12)
        return (NIL);

    tm->tm_mon = n;

    /* finish the work */
    if (tm->tm_year > 1900)
        tm->tm_year -= 1900;
    else if (tm->tm_year < 70)
        tm->tm_year += 100;

    tm->tm_isdst = 0;

    return (mktime(tm));
}

/* ====================================================================== */

/* response_date_string() ************************************************
 *
 * Print HTTP format date to output buffer
 ************************************************************************/

void response_date_string(struct buffer *b, time_t time)
{
    static char *date_day[7] = {
        "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
    };
    static char *date_month[12] = {
        "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep",
        "Oct",
        "Nov", "Dec"
    };
    struct tm *tm = gmtime(&time);

    bprintf(b, "%s, %c%c %s %lu %c%c:%c%c:%c%c GMT",
            date_day[tm->tm_wday],
            '0' + ((tm->tm_mday / 10) % 10),
            '0' + ((tm->tm_mday) % 10),
            date_month[tm->tm_mon],
            (unsigned long) tm->tm_year + 1900,
            '0' + ((tm->tm_hour / 10) % 10),
            '0' + ((tm->tm_hour) % 10),
            '0' + ((tm->tm_min / 10) % 10),
            '0' + ((tm->tm_min) % 10),
            '0' + ((tm->tm_sec / 10) % 10), '0' + ((tm->tm_sec) % 10));
}

/* ====================================================================== */

/* response_header_prime() ***********************************************
 *
 * Prime response_hdrs component of request structure. Two calls for single
 * requests implies a logic fault in the main program, typically two
 * separate calls to response_html because cmd_something routine failed to
 * return correctly after page was generated.
 *   request: Complete HTTP request
 *
 * Returns: (Empty) buffer that response headers will be written into
 ************************************************************************/

static struct buffer *response_header_prime(struct request *request)
{
    if (request->response_hdrs)
        log_fatal("Request \"%s\" generated two responses: shutting down",
                  request->request);

    request->response_hdrs
        = buffer_create(request->pool, RESPONSE_HDR_BLOCK_SIZE);

    return (request->response_hdrs);
}

/* response_header_start() ***********************************************
 *
 * Set up HTTP response based on request and user-agent configuration
 * and defaults. A few extra headers will be added for each type of
 * response.
 *  request: Complete HTTP requests that we are responding to
 *           (includes user-agent structure for this request)
 *   status: HTTP response code to this request
 *
 ************************************************************************/

static void
response_header_start(struct request *request, unsigned long status)
{
    struct user_agent *ua = request->user_agent;
    char *s;
    time_t now = time(NIL);
    struct buffer *b = request->response_hdrs;

    /* Work out if this connection or its proxy is going to remain alive
     * after this r */
    if (request->iseof)
        request->persist = NIL;
    else if (request->error)
        request->persist = NIL;
    else if (ua->use_persist == NIL)
        request->persist = NIL;
    else if (ua->use_http_1_1 && (request->major == 1)
             && (request->minor == 1)) {
        /* HTTP 1.1 defaults to Persistent connections */
        if ((s = assoc_lookup(request->hdrs, "connection")) &&
            !strcasecmp(s, "close"))
            request->persist = NIL;
        request->use_http_1_1 = T;
    } else {
        /* HTTP 1.0 defaults to connection close */
        if (!((s = assoc_lookup(request->hdrs, "connection")) &&
              (!strcasecmp(s, "Keep-Alive"))))
            request->persist = NIL;
        request->use_http_1_1 = NIL;
    }

    request->status = status;

    if (request->use_http_1_1) {
        /* HTTP 1.1 response */
        bprintf(b, "HTTP/1.1 %lu %s" CRLF, status,
                response_status_text(status));
        bputs(b, "Date: ");
        response_date_string(b, now);
        bputs(b, "" CRLF);
        bprintf(b, "Server: Prayer/%s" CRLF, response_version);
        bputs(b, "MIME-Version: 1.0" CRLF);
        bputs(b, "Allow: GET, HEAD, POST" CRLF);

        if (request->persist)
            bputs(b, "Connection: Keep-Alive" CRLF);
        else
            bputs(b, "Connection: close" CRLF);
        return;
    }

    /* HTTP 1.0 response */
    bprintf(b, "HTTP/1.0 %lu %s" CRLF, status,
            response_status_text(status));
    bputs(b, "Date: ");
    response_date_string(b, time(NIL));
    bputs(b, "" CRLF);
    bprintf(b, "Server: Prayer/%s" CRLF, response_version);
    bputs(b, "MIME-Version: 1.0" CRLF);
    bputs(b, "Allow: GET, HEAD, POST" CRLF);

    /* Send Keep-Alive if HTTP 1.0 only if client requested Keep-Alive */
    if (request->persist)
        bputs(b, "Connection: Keep-Alive" CRLF);
}

/* response_header_end() ***********************************************
 *
 * Finish off response_hdrs block. Doesn't actually do anything more
 * than add a CRLF separator line to the header block. Could eliminate
 * this routine by doing this in response_send
 *  request: Complete HTTP requests that we are responding to
 *
 ************************************************************************/

static void response_header_end(struct request *request)
{
    struct buffer *b = request->response_hdrs;

    bputs(b, "" CRLF);
}

/* ====================================================================== */

/* response_error() ******************************************************
 *
 * Set up a (HTML) error response based on the given status code
 *  request: Complete HTTP requests that we are responding to
 *   status: Error code
 *
 ************************************************************************/

void response_error(struct request *request, unsigned long status)
{
    struct config *config = request->config;
    struct buffer *b;
    char *errmsg = response_status_text(request->status = status);
    char *title = pool_printf(request->pool, "%lu %s", status, errmsg);

    if (buffer_size(request->write_buffer) > 0)
        request->write_buffer = buffer_create(request->pool,
                                              PREFERRED_BUFFER_BLOCK_SIZE);

    b = request->write_buffer;

    /* Generate a nice clean body for the error */

    html_common_start(config, b, title);
    bprintf(b, "<h2>%s</h2>" CRLF, errmsg);

    if (request->url && request->url[0]) {
        if (status == 404) {
            bputs(b, "The requested URL \"");
            html_common_quote_string(b, request->url);
            bputs(b, "\" was not found on this server." CRLF);
        } else {
            bputs(b, "Error status from URL: \"");
            html_common_quote_string(b, request->url);
            bputs(b, "\"." CRLF);
        }
    } else {
        bprintf(b, "Error status %d (%s) from HTTP request:" CRLF,
                status, errmsg);
        if (request->request) {
            bputs(b, "<br>" CRLF);
            html_common_quote_string(b, request->request);
            bputs(b, "</br>" CRLF);
        }
    }

    html_common_end(b);

    /* Now generate the headers */
    b = response_header_prime(request);
    request->error = T;
    response_header_start(request, status);
    if (request->use_http_1_1)
        bputs(b, ("Cache-control: no-cache, no-store, must-revalidate, "
                  "post-check=0, pre-check=0" CRLF));
    bputs(b, "Pragma: no-cache" CRLF);
    bputs(b, "Expires: ");
    response_date_string(b, time(NIL));
    bputs(b, "" CRLF);
    bprintf(b, "Content-Type: text/html; charset=UTF-8" CRLF);
    bprintf(b, "Content-Length: %lu" CRLF,
            buffer_size(request->write_buffer));
    response_header_end(request);
}

/* ====================================================================== */

/* response_html() *******************************************************
 *
 * Set up HTTP headers for a HTML response.
 *  request: Complete HTTP request
 *   status: Status code
 *
 ************************************************************************/

void response_html(struct request *request, unsigned long status)
{
    struct buffer *b = response_header_prime(request);
    unsigned long size = buffer_size(request->write_buffer);

#ifdef GZIP_ENABLE
    if (request->allow_gzip && request->user_agent->use_gzip) {
        if (request->use_gzip || request->use_x_gzip) {
            request->gzip_buffer = gzip(request->write_buffer);
            size = buffer_size(request->gzip_buffer);
        }
    }
#endif

    /* Generate simple header block for HTML */
    response_header_start(request, status);
    bputs(b, "Content-Type: text/html; charset=UTF-8" CRLF);

    if (request->use_http_1_1)
        bputs(b, ("Cache-control: no-cache, no-store, must-revalidate, "
                  "post-check=0, pre-check=0" CRLF));
    else
        bputs(b, "Pragma: no-cache" CRLF);
    bputs(b, "Expires: ");
    response_date_string(b, time(NIL));
    bputs(b, "" CRLF);
    bprintf(b, "Content-Length: %lu" CRLF, size);

#ifdef GZIP_ENABLE
    if (request->gzip_buffer) {
        if (request->use_gzip)
            bputs(b, "Content-Encoding: gzip" CRLF);
        else if (request->use_x_gzip)
            bputs(b, "Content-Encoding: x-gzip" CRLF);
    }
#endif

    response_header_end(request);
}

/* response_text() *******************************************************
 *
 * Set up HTTP headers for a text/plain response.
 *  request: Complete HTTP request
 *   status: Status code
 *
 ************************************************************************/

void response_text(struct request *request, unsigned long status)
{
    struct buffer *b = response_header_prime(request);
    unsigned long size = buffer_size(request->write_buffer);

#ifdef GZIP_ENABLE
    if (request->allow_gzip && request->user_agent->use_gzip) {
        if (request->use_gzip || request->use_x_gzip) {
            request->gzip_buffer = gzip(request->write_buffer);
            size = buffer_size(request->gzip_buffer);
        }
    }
#endif

    /* Generate simple header block for simple text */
    response_header_start(request, status);
    bputs(b, "Content-Type: text/plain; charset=utf-8"CRLF);
    if (request->use_http_1_1)
        bputs(b, ("Cache-control: no-cache, no-store, must-revalidate, "
                  "post-check=0, pre-check=0" CRLF));
    else
        bputs(b, "Cache-control: no-cache" CRLF);
    bputs(b, "Expires: ");
    response_date_string(b, time(NIL));
    bputs(b, "" CRLF);
    bprintf(b, "Content-Length: %lu" CRLF, size);

#ifdef GZIP_ENABLE
    if (request->gzip_buffer) {
        if (request->use_gzip)
            bputs(b, "Content-Encoding: gzip" CRLF);
        else if (request->use_x_gzip)
            bputs(b, "Content-Encoding: x-gzip" CRLF);
    }
#endif

    response_header_end(request);
}

/* response_raw() ********************************************************
 *
 * Set up HTTP headers for a raw (binary) response.
 *  request: Complete HTTP request
 *     name: Name of attachment (most user-agents ignore this?)
 *     type: Typically "application/octet-stream". Conceivable that other
 *           values have some use.
 *   status: Status code
 *
 * Notes: Content-Disposition defined by RFC 2183
 *        MSIE has numerous bugs related to file downloads.
 *
 ************************************************************************/

void
response_raw(struct request *request, char *name, char *type,
             unsigned long status)
{
    struct buffer *b = response_header_prime(request);
    unsigned long size = buffer_size(request->write_buffer);
    char *ua;

#ifdef GZIP_ENABLE
    if (request->allow_gzip && request->user_agent->use_gzip) {
        if ((ua = assoc_lookup(request->hdrs, "user-agent")) &&
            strstr(ua, "Opera")) {
            /* No gzip for Opera attachment downloads: Opera is broken */
        } else if (request->use_gzip || request->use_x_gzip) {
            request->gzip_buffer = gzip(request->write_buffer);
            size = buffer_size(request->gzip_buffer);
        }
    }
#endif

    if (name) {
        char *s;

        /* Isolate last component of name */
        if ((s = strrchr(name, '/')))
            name = s + 1;
        else if ((s = strrchr(name, '\\')))
            name = s + 1;

        /* Replace " characters from name to prevent quoting problems */
        /* Tab characters cause problems for MSIE */
        name = pool_strdup(request->pool, name);

        for (s = name; *s; s++) {
            if (*s == '"')
                *s = '_';
            if (*s == '\t')
                *s = '_';
        }
    } else
        name = "unknown";

    if ((ua = assoc_lookup(request->hdrs, "user-agent")) &&
        !strncasecmp(ua, "Mozilla/4.0 (compatible; MSIE ",
                     strlen("Mozilla/4.0 (compatible; MSIE ")) &&
        !strstr(ua, "Opera")) {
        /* Handle MSIE as special case: very broken! */
        /* NB: Opera may claim to be MSIE, but it really isn't! */

        /* Switch off HTTP/1.1 and KeepAlive for this request */
        request->user_agent->use_http_1_1 = NIL;
        request->user_agent->use_persist = NIL;

        /* Generate simple header block for simple text */
        response_header_start(request, status);
        bputs(b, "Content-Transfer-Encoding: binary" CRLF);

        /* Stolen from IMP */
        bprintf(b, "Content-Type: application/x-msdownload" CRLF);
        bprintf(b, "Content-Disposition: attachment; filename=\"%s\"" CRLF,
                string_url_encode(request->pool, name));

        /* MSIE can't cope with download links which expire immediately? */
        bputs(b, "Expires: ");
        response_date_string(b, time(NIL) + (10 * 60)); /* 10 mins */
        bputs(b, "" CRLF);
    } else {
        /* Everything else seems to cope just fine with following code */
        response_header_start(request, status);
        bputs(b, "Content-Transfer-Encoding: binary" CRLF);

        /* Stolen from Squirelmail */
        bprintf(b, "Content-Type: %s; name=\"%s\"" CRLF, type, name);
        bprintf(b, "Content-Disposition: attachment; filename=\"%s\"" CRLF,
                name);

        if (request->use_http_1_1)
            bputs(b,
                  ("Cache-control: no-cache, no-store, must-revalidate, "
                   "post-check=0, pre-check=0" CRLF));
        else
            bputs(b, "Pragma: no-cache" CRLF);
        bputs(b, "Expires: ");
        response_date_string(b, time(NIL));
        bputs(b, "" CRLF);
    }

    bprintf(b, "Content-Length: %lu" CRLF, size);
#ifdef GZIP_ENABLE
    if (request->gzip_buffer) {
        if (request->use_gzip)
            bputs(b, "Content-Encoding: gzip" CRLF);
        else if (request->use_x_gzip)
            bputs(b, "Content-Encoding: x-gzip" CRLF);
    }
#endif
    response_header_end(request);
}

/* ====================================================================== */

/* response_file() *******************************************************
 *
 * Set up HTTP headers for a raw (binary) response of known type.
 *           request: Complete HTTP request
 *            method: Method (can get from request!)
 *          filename: File to send
 *           timeout: Timeout for this file (typically deefined by config).
 * if_modified_since: HTTP header if exists.
 *
 * Returns:   T => response buffers set up successfully
 *          NIL => no such file. Up to client routine to send error page
 ************************************************************************/

BOOL
response_file(struct request *request,
              HTTP_METHOD method,
              char *filename, unsigned long timeout,
              char *if_modified_since)
{
    struct buffer *b;
    struct stat sbuf;
    char *e = strrchr(filename, '.');
    FILE *file;
    int c;
    char *s;

    s = filename;

    /* Check file rather than directory */
    if ((stat(s, &sbuf) != 0) || (S_ISDIR(sbuf.st_mode))) {
        response_error(request, 404);
        return (NIL);
    }

    if (if_modified_since && (method == GET)) {
        if (response_decode_date(if_modified_since) > sbuf.st_mtime) {
            /* Not modified */
            response_header_prime(request);
            response_header_start(request, 304);
            response_header_end(request);
            return (T);
        }
    }

    if ((file = fopen(s, "r")) == NIL) {
        request->error = T;
        response_error(request, 404);
        return (NIL);
    }

    b = response_header_prime(request);
    response_header_start(request, 200);        /* Success */

    if (e) {
        if (!strcasecmp(e, ".html") || !strcasecmp(e, ".htm"))
            bputs(b, "Content-Type: text/html; charset=utf-8" CRLF);
        else if (!strcasecmp(e, ".gif"))
            bputs(b, "Content-Type: image/gif" CRLF);
        else if (!strcasecmp(e, ".jpg"))
            bputs(b, "Content-Type: image/jpeg" CRLF);
        else if (!strcasecmp(e, ".png"))
            bputs(b, "Content-Type: image/png" CRLF);
        else if (!strcasecmp(e, ".ico"))
            bputs(b, "Content-Type: image/png" CRLF);
        else if (!strcasecmp(e, ".css"))
            bputs(b, "Content-Type: text/css" CRLF);
	else if (!strcasecmp(e, ".js"))
	    bputs(b, "Content-Type: text/javascript" CRLF);
        else
            bputs(b, "Content-Type: text/plain" CRLF);
    } else
        bputs(b, "Content-Type: text/plain" CRLF);

    bprintf(b, "Content-Length: %lu" CRLF, sbuf.st_size);

    /* Following stolen from Apache:
     *
     * Make an ETag header out of various pieces of information. We use
     * the last-modified date and, if we have a real file, the
     * length and inode number - note that this doesn't have to match
     * the content-length (i.e. includes), it just has to be unique
     * for the file.
     *
     * If the request was made within a second of the last-modified date,
     * we send a weak tag instead of a strong one, since it could
     * be modified again later in the second, and the validation
     * would be incorrect.
     */

    bprintf(b,
            "ETag: \"%lx-%lx-%lx\"" CRLF,
            (unsigned long) sbuf.st_ino,
            (unsigned long) sbuf.st_size, (unsigned long) sbuf.st_mtime);

    bputs(b, "Expires: ");
    response_date_string(b, (time(NIL) + timeout));
    bputs(b, "" CRLF);

    bputs(b, "Last-Modified: ");
    response_date_string(b, sbuf.st_mtime);
    bputs(b, "" CRLF);
    response_header_end(request);

    /* Now punt data to the write buffer */
    b = request->write_buffer;

    if (method != HEAD) {
        while ((c = fgetc(file)) != EOF)
            bputc(b, c);
    }
    fclose(file);
    return (T);
}

/* ====================================================================== */

/* response_0.9_error() **************************************************
 *
 * Set up a (HTML) error page for unqualified requests
 *   request: Complete HTTP request
 ************************************************************************/

void response_0_9_error(struct request *request)
{
    struct config *config = request->config;
    struct buffer *b;

    if (buffer_size(request->write_buffer) > 0) {
        buffer_free(request->write_buffer);
        request->write_buffer
            = buffer_create(request->pool, PREFERRED_BUFFER_BLOCK_SIZE);
    }

    b = request->write_buffer;
    html_common_start(config, b, "Unsupported Browser");
    bprintf(b, "This application needs a HTTP/1.0 or better" CRLF);
    bprintf(b, "browser which supports POST" CRLF);
    html_common_end(b);
    response_html(request, 501);        /* Not implemented */
}

/* ====================================================================== */

/* response_redirect() **************************************************
 *
 * Set up HTTP headers for a HTTP temporary redirect (302 response)
 *   request: Complete HTTP request
 *  location: Target for redirection
 ************************************************************************/

void response_redirect(struct request *request, char *location)
{
    struct buffer *b = response_header_prime(request);

    /* Temporary redirection */
    response_header_start(request, 302);
    bprintf(b, "Location: %s" CRLF, location);
    bputs(b, "Content-Length: 0" CRLF);
    response_header_end(request);

    /* Clear out any body data which we might have accumulated */
    if (buffer_size(request->write_buffer) > 0)
        request->write_buffer = buffer_create(request->pool,
                                              PREFERRED_BUFFER_BLOCK_SIZE);
}

/* response_cookie_redirect() ********************************************
 *
 * Set up HTTP headers for a HTTP temporary redirect (302 response)
 * which also sets up a Cookie.
 *      request: Complete HTTP request
 *     location: Target for redirection
 *   cookie_key: Cookie Key
 * cookie_value: Cookie Value
 *         path: Restrict Cookie to this path
 *       domain: Restrict Cookie to this domain
 *       secure: Mark Cookie as SSL-only
 ************************************************************************/

void
response_cookie_redirect(struct request *request, char *location,
			 char *cookie_key, char *cookie_value,
			 char *path, char *domain, BOOL secure)
{
    struct buffer *b = response_header_prime(request);

    /* Generate simple header block */
    /* Temporary redirection */
    response_header_start(request, 302);
    bprintf(b, "Location: %s" CRLF, location);
    bputs(b, "Content-Length: 0" CRLF);

#if 1                           /* XXX Experiment: do "sesion cookies" work reliably? */
    bprintf(b, "Set-Cookie: %s=%s; path=%s; domain=%s",
            cookie_key, cookie_value, path, domain);
    if (secure)
	    bputs(b, "; secure");
    bputs(b, "" CRLF);
#else
    bprintf(b, "Set-Cookie: %s=%s; path=%s; domain=%s",
            cookie_key, cookie_value, path, domain);
    if (secure)
	    bputs(b, "; secure");
    bputs(b, "; expires=");
    response_date_string(b, time(NIL) + (24 * 60 * 60));
    bputs(b, "" CRLF);
#endif

    response_header_end(request);

    /* Clear out any body data which we might have accumulated */
    if (buffer_size(request->write_buffer) > 0)
        request->write_buffer = buffer_create(request->pool,
                                              PREFERRED_BUFFER_BLOCK_SIZE);
}

/* response_clear_cookie_redirect() **************************************
 *
 * Set up HTTP headers for a HTTP temporary redirect (302 response)
 * which also clears a Cookie.
 *      request: Complete HTTP request
 *     location: Target for redirection
 *   cookie_key: Cookie Key
 * cookie_value: Cookie Value
 *         path: Restrict Cookie to this path
 *       domain: Restrict Cookie to this domain
 *       secure: Mark Cookie as SSL-only
 ************************************************************************/

void
response_clear_cookie_redirect(struct request *request, char *location,
			       char *cookie_key, char *cookie_value,
			       char *path, char *domain, BOOL secure)
{
    struct buffer *b = response_header_prime(request);

    /* Generate simple header block */
    /* Temporary redirection */
    response_header_start(request, 302);
    bprintf(b, "Location: %s" CRLF, location);
    bputs(b, "Content-Length: 0" CRLF);

    bprintf(b, "Set-Cookie: %s=%s; path=%s; domain=%s",
            cookie_key, cookie_value, path, domain);
    if (secure)
	    bputs(b, "; secure");
    bputs(b, "; Max-Age=0; expires=");
    response_date_string(b, time(NIL));
    bputs(b, "" CRLF);
    response_header_end(request);

    /* Clear out any body data which we might have accumulated */
    if (buffer_size(request->write_buffer) > 0)
        request->write_buffer = buffer_create(request->pool,
                                              PREFERRED_BUFFER_BLOCK_SIZE);
}

/* ====================================================================== */

/* response_telemetry_all() **********************************************
 *
 * Record full request and response in telemetry file for debugging.
 * which also clears a Cookie.
 *      request: Complete HTTP request
 ************************************************************************/

static void response_telemetry_all(struct request *request)
{
    BOOL body = (request->method != HEAD);
    unsigned long count;
    unsigned long request_size;
    unsigned long size;
    char *block;
    char *s;
    struct buffer *b;
    int c;

    request_size = buffer_size(request->read_buffer);

    if (body)
        size = (request_size +
                buffer_size(request->response_hdrs) +
                buffer_size(request->write_buffer));
    else
        size = request_size + buffer_size(request->response_hdrs);

    block = pool_alloc(request->pool, size + 1);
    s = block;

    b = request->read_buffer;
    buffer_rewind(b);
    count = 0;
    while (((c = bgetc(b)) != EOF) && (count < request_size)) {
        *s++ = c;
        count++;
    }

    b = request->response_hdrs;
    buffer_rewind(b);
    while ((c = bgetc(b)) != EOF)
        *s++ = c;

    if (body) {
        b = request->write_buffer;
        buffer_rewind(b);
        while ((c = bgetc(b)) != EOF)
            *s++ = c;
    }

    *s = '\0';

    write(request->telemetry_fd, block, size);
}

/* response_telemetry() ***************************************************
 *
 * Record request and response hdrs in telemetry file for debugging.
 *      request: Complete HTTP request
 ************************************************************************/

static void response_telemetry(struct request *request)
{
    unsigned long count;
    unsigned long request_size;
    unsigned long size;
    char *block;
    char *s;
    struct buffer *b;
    int c;

    request_size = request->method_size + request->hdrs_size + 1;
    size = request_size + buffer_size(request->response_hdrs);
    block = pool_alloc(request->pool, size + 1);
    s = block;

    b = request->read_buffer;
    buffer_rewind(b);
    count = 0;
    while (((c = bgetc(b)) != EOF) && (count < request_size)) {
        *s++ = c;
        count++;
    }

    b = request->response_hdrs;
    buffer_rewind(b);
    while ((c = bgetc(b)) != EOF)
        *s++ = c;

    *s = '\0';

    write(request->telemetry_fd, block, size);
}

/* ====================================================================== */

/* XXX Temporary */

static void response_dump(struct request *request)
{
    struct buffer *b = request->write_buffer;
    FILE *file;
    int c;

    if ((file=fopen("last-response.html", "w")) == NULL)
        return;

    buffer_rewind(b);
    while ((c = bgetc(b)) != EOF)
        fputc(c, file);

    fclose(file);
}

/* ====================================================================== */

/* response_send() *******************************************************
 *
 * Actually send a complete HTTP response after header and body setup
 *  request: Complete HTTP request, including links to response structure
 *           and iostream that connects up to client.
 ************************************************************************/

void response_send(struct request *request)
{
    struct iostream *stream = request->stream;
    struct buffer *b;
    BOOL body = (request->method != HEAD);
    int c;

    if (!request->response_hdrs)
        log_fatal("Request \"%s\" generated no response",
                  request->request);

    /* writev possible here? Only on plaintext connections! */
    b = request->response_hdrs;
    buffer_rewind(b);
    while ((c = bgetc(b)) != EOF)
        ioputc(c, stream);

    if (body) {
        if (request->gzip_buffer)
            b = request->gzip_buffer;
        else
            b = request->write_buffer;

        buffer_rewind(b);
        while ((c = bgetc(b)) != EOF)
            ioputc(c, stream);
    }

    if (request->dump)
        response_dump(request);

    if (request->telemetry_all)
        response_telemetry_all(request);
    else if (request->telemetry)
        response_telemetry(request);
}
