/*
 * Copyright (c) Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2018 All Rights Reserved
 *
 * Licensed under CLOUD LINUX LICENSE AGREEMENT
 * http://cloudlinux.com/docs/LICENSE.TXT
 *
 * The part the support library for lsapi & proxy_lsapi Apache modules
 * author Alexander Demeshko <ademeshko@cloudlinux.com>
 *
 */

#include <lscapi_util.h>

#include <util_script.h>
#include <http_request.h>
#include <httpd.h>

#include <apr_lib.h>
#include <apr_date.h>

const char troubleshooting[] = "http://docs.cloudlinux.com/mod_lsapi_troubleshooting.html";

struct hdrs_flags
{
    int content_type_found;
    int content_type_event_stream;
    int x_accel_buffering_found;
    int x_accel_buffering_no;
    int www_authenticate_found;
};

struct hdrs_context
{
    const lscapi_resp_hdr_t *respHdr;
    int curNum;
    int curHdrSize;
    apr_off_t curOff;
    request_rec *r;
    lsapi_svr_conf_t *svrcfg;
    lsapi_dir_conf_t *dircfg;
    int cgi_status;
    const char *status_line;
    struct hdrs_flags *hdrsFlagsPtr;
};

static int getsfunc_HDRBUF(char *w, int len, struct hdrs_context *c)
{

    if (c->curNum >= c->respHdr->hdrNum) // normal exit
    {
        w[0] = '\0';
        return 1;
    }

    if (c->curOff > c->respHdr->dataLen)
    {
        if (c->svrcfg->debug_enabled)
        {
            lscapi_rlog(APLOG_NOTICE, 0, c->r,
                        "Wrong packet format: offset %" APR_OFF_T_FMT " is more than datalen %"APR_SIZE_T_FMT,
                        c->curOff, c->respHdr->dataLen);
        }
        return -1;
    }

    if (c->curHdrSize == 0)
    {
        c->curHdrSize = c->respHdr->hdrSizes[c->curNum];
    }

    if (c->hdrsFlagsPtr->content_type_found == 0)
    {
        if (strncasecmp(c->respHdr->respBuffer + c->curOff, "Content-Type:", 13) == 0)
        {
            c->hdrsFlagsPtr->content_type_found = 1;
            const char *ptr = c->respHdr->respBuffer + c->curOff + 13;
            if (*ptr == ' ')
            {
                ptr++;
            }
            if (strncasecmp(ptr, "text/event-stream", 17) == 0)
            {
                c->hdrsFlagsPtr->content_type_event_stream = 1;
            }
        }
    }

    if (c->hdrsFlagsPtr->x_accel_buffering_found == 0)
    {
        if (strncasecmp(c->respHdr->respBuffer + c->curOff, "X-Accel-Buffering:", 18) == 0)
        {
            c->hdrsFlagsPtr->x_accel_buffering_found = 1;
            const char *ptr = c->respHdr->respBuffer + c->curOff + 18;
            if (*ptr == ' ')
            {
                ptr++;
            }
            if (strncasecmp(ptr, "no", 2) == 0)
            {
                c->hdrsFlagsPtr->x_accel_buffering_no = 1;
            }
        }
    }

    if (c->hdrsFlagsPtr->www_authenticate_found == 0)
    {
        if (strncasecmp(c->respHdr->respBuffer + c->curOff, "WWW-Authenticate:", 17) == 0)
        {
            c->hdrsFlagsPtr->www_authenticate_found = 1;
        }
    }

    if (c->curHdrSize < len)
    {
        memcpy(w, c->respHdr->respBuffer + c->curOff, c->curHdrSize);
        c->curNum++;
        c->curOff += c->curHdrSize;
        c->curHdrSize = 0;
    }
    else
    {
        memcpy(w, c->respHdr->respBuffer + c->curOff, len-1);
        w[len-1] = '\0';

        c->curOff += len-1;
        c->curHdrSize -= len-1;
    }

    return 1;
}

#ifdef LSCAPI_WITH_DUMP_HEADER
/*
static void DumpHeaders(request_rec *r, const lscapi_resphdr_t *hdrsInfoPtr) {
    static unsigned long serialNo;
    char fname[256];


    snprintf(fname, sizeof fname, "/tmp/%s-%08lu.log", r->hostname, serialNo);
    serialNo++;

    FILE *fptr = fopen(fname, "wb");
    if (fptr) {
        lscapi_rlog(APLOG_ERR, 0, r,
                    "Dump headers into (%s)", fname);
        fwrite(hdrsInfoPtr->buf, 1, hdrsInfoPtr->dataLen, fptr);
        fclose(fptr);
    }
}
*/
#endif


// As RESPONSE_CODES in Apache 2.4 but should be the same also for Apache 2.2
#define LSAPI_RESPONSE_CODES 83

/*
http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
*/
static const char * const status_lines[LSAPI_RESPONSE_CODES+1] =  // 1 for UNKNOWN
{
    "100 Continue",
    "101 Switching Protocols",
    "102 Processing",
#define LEVEL_200  3
    "200 OK",
    "201 Created",
    "202 Accepted",
    "203 Non-Authoritative Information",
    "204 No Content",
    "205 Reset Content",
    "206 Partial Content",
    "207 Multi-Status",
    "208 Already Reported",
    NULL, /* 209 */
    NULL, /* 210 */
    NULL, /* 211 */
    NULL, /* 212 */
    NULL, /* 213 */
    NULL, /* 214 */
    NULL, /* 215 */
    NULL, /* 216 */
    NULL, /* 217 */
    NULL, /* 218 */
    NULL, /* 219 */
    NULL, /* 220 */
    NULL, /* 221 */
    NULL, /* 222 */
    NULL, /* 223 */
    NULL, /* 224 */
    NULL, /* 225 */
    "226 IM Used",
#define LEVEL_300 30
    "300 Multiple Choices",
    "301 Moved Permanently",
    "302 Moved Temporarily",    // instead of Apache "302 Found",
    "303 See Other",
    "304 Not Modified",
    "305 Use Proxy",
    NULL, /* 306 */
    "307 Temporary Redirect",
    "308 Permanent Redirect",
#define LEVEL_400 39
    "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",
    "416 Requested Range Not Satisfiable",
    "417 Expectation Failed",
    NULL, /* 418 */
    NULL, /* 419 */
    NULL, /* 420 */
    "421 Misdirected Request",
    "422 Unprocessable Entity",
    "423 Locked",
    "424 Failed Dependency",
    NULL, /* 425 */
    "426 Upgrade Required",
    NULL, /* 427 */
    "428 Precondition Required",
    "429 Too Many Requests",
    NULL, /* 430 */
    "431 Request Header Fields Too Large",
#define LEVEL_500 71
    "500 Internal Server Error",
    "501 Not Implemented",
    "502 Bad Gateway",
    "503 Service Unavailable",
    "504 Gateway Timeout",
    "505 HTTP Version Not Supported",
    "506 Variant Also Negotiates",
    "507 Insufficient Storage",
    "508 Loop Detected",
    NULL, /* 509 */
    "510 Not Extended",
    "511 Network Authentication Required",
#define LSAPI_RESPONSE_CODE_UNKNOWN 83
    "UKNOWN"
};

static int lsapi_index_of_response(int status, int with_unknown)
{
    static int shortcut[6] = {0, LEVEL_200, LEVEL_300, LEVEL_400,
    LEVEL_500, LSAPI_RESPONSE_CODES};
    int i, pos;

    if (status < 100)
    {               /* Below 100 is illegal for HTTP status */
        return LEVEL_500;
    }

    for (i = 0; i < 5; i++)
    {
        status -= 100;
        if (status < 100)
        {
            pos = (status + shortcut[i]);
            if (pos < shortcut[i + 1] && status_lines[pos] != NULL)
            {
                return pos;
            }
            else
            {
                /* status unknown (falls in gap) */
                return with_unknown ? LSAPI_RESPONSE_CODE_UNKNOWN : LEVEL_500;
            }
        }
    }
    return LEVEL_500;                         /* 600 or above is also illegal */
}

const char* lsapi_get_status_line(int status)
{
    return status_lines[lsapi_index_of_response(status, 0)];
}

static const char* lsapi_get_status_line_with_null(int status)
{
    int n = lsapi_index_of_response(status, 1);
    return n == LSAPI_RESPONSE_CODE_UNKNOWN ? NULL : status_lines[n];
}

#define SET_STATUS_WITH_LINE(r, st)  do { (r)->status = (st); (r)->status_line = lsapi_get_status_line((r)->status); } while(0)

static void set_status_line(request_rec *r, lsapi_svr_conf_t *svrcfg, struct hdrs_context *contextPtr)
{
    const char *status_line = lsapi_get_status_line_with_null(r->status);
    if (status_line)
    {
        r->status_line = status_line;
        if (svrcfg->debug_enabled)
        {
            lscapi_rlog(APLOG_NOTICE, 0, r, "... and set status line to standard one (%s) as status is known", r->status_line);
        }
    } else if (contextPtr->cgi_status == r->status)
    {
        r->status_line = contextPtr->status_line;
        if (svrcfg->debug_enabled)
        {
            lscapi_rlog(APLOG_NOTICE, 0, r, "... and set status line to context one (%s) as status is unknown", r->status_line);
        }
    } else
    {
        char buf[8];
        snprintf(buf, sizeof buf, "%3d ", r->status);
        r->status_line = apr_pstrdup(r->pool, buf);
        if (svrcfg->debug_enabled)
        {
            lscapi_rlog(APLOG_NOTICE, 0, r, "... and set status line to pseudo one (%s) as status is unknown and we cannot use context one", r->status_line);
        }
    }
}


static int read_from_backend(lscapi_rec *lscapi, lsphp_conn_t *backend, request_rec *r,
                             lsapi_svr_conf_t *svrcfg, lsapi_dir_conf_t *dircfg,
                             int drain,
                             const struct hdrs_flags *hdrsFlagsPtr,
                             char *errbuf, size_t errlen)
{
    int rc;
    char buf[LSAPI_MAX_HEADER_LEN];
    unsigned eventMask;
    apr_bucket_brigade *ob = NULL;
    apr_status_t rv;

    if (drain == 0)
    {
        ob = apr_brigade_create(r->pool, r->connection->bucket_alloc);
    }

    while (1)
    {
        size_t bufLen = sizeof buf;
        eventMask = 0;
        rc = lscapi_receive_response_chunk(backend, buf, &bufLen, &eventMask, errbuf, errlen);
        if (rc != 0) {
            lscapi_rlog(APLOG_ERR, 0, r, "Error receiving response: %s", errbuf);
            lscapi_set_error(lscapi);
            if (drain == 0)
            {
                SET_STATUS_WITH_LINE(r, dircfg->err_backend_recvrsp);  //HTTP_SERVICE_UNAVAILABLE
                apr_brigade_cleanup(ob);
            }
            return -1;
        }

        if (eventMask & LSCAPI_RESPONSE_FINISHED)
        {
            if (drain == 0)
            {
                apr_bucket *eosB = apr_bucket_eos_create(r->connection->bucket_alloc);
                APR_BRIGADE_INSERT_TAIL(ob, eosB);
                rv = ap_pass_brigade(r->output_filters, ob);
                if (rv == AP_FILTER_ERROR)
                {
                    lscapi_rlog(APLOG_WARNING, rv, r, "HTTP output filters(1) error detected");
                    apr_brigade_cleanup(ob);
                    return -1;
                }
            }
            break;
        }

        if (drain == 0)
        {
            apr_bucket *b = apr_bucket_transient_create(buf, bufLen, r->connection->bucket_alloc);
            APR_BRIGADE_INSERT_TAIL(ob, b);
            rv = ap_pass_brigade(r->output_filters, ob);
            if (rv == AP_FILTER_ERROR)
            {
                lscapi_rlog(APLOG_WARNING, rv, r, "HTTP output filters(2) error detected");
                apr_brigade_cleanup(ob);
                return -1;
            }
            if (svrcfg->debug_enabled)
            {
                lscapi_rlog(APLOG_NOTICE, 0, r, "data chunk received; len %lu", bufLen);
            }

            if (hdrsFlagsPtr->x_accel_buffering_no
              || hdrsFlagsPtr->content_type_event_stream
              || dircfg->output_buffering == 0)
            {
                APR_BRIGADE_INSERT_TAIL(ob, apr_bucket_flush_create(r->connection->bucket_alloc));
                if (svrcfg->debug_enabled)
                {
                    lscapi_rlog(APLOG_NOTICE, 0, r,
                                "flush output buffer: %d; %d; %d",
                                hdrsFlagsPtr->x_accel_buffering_no,
                                hdrsFlagsPtr->content_type_event_stream,
                                dircfg->output_buffering);
                }
                rv = ap_pass_brigade(r->output_filters, ob);
                if (rv == AP_FILTER_ERROR)
                {
                    lscapi_rlog(APLOG_WARNING, rv, r, "HTTP output filters(3) error detected");
                    apr_brigade_cleanup(ob);
                    return -1;
                }
            }

        }

    } //while(!done)

    if (drain == 0)
    {
        //apr_brigade_cleanup(ib);
        apr_brigade_cleanup(ob);
    }
    return 0;
}

static int
lscapi_set_cookie(void *v, const char *key, const char *val)
{
    apr_table_addn(v, key, val);
    return 1;
}

#define HTTP_UNSET (-HTTP_OK)

static int
lscapi_scan_script_header_err_core(request_rec *r, char *buffer,
                                    int (*getsfunc) (char *, int, struct hdrs_context *),
                                    struct hdrs_context *c)
{
    char *w, *left;
    int p;
    apr_table_t *merge;
    apr_table_t *cookie_table;
    int first_header = 1;

    *buffer = '\0';
    w = buffer;

    /* temporary place to hold headers to merge in later */
    merge = apr_table_make(r->pool, 10);

    cookie_table = apr_table_make(r->pool, 2);
    apr_table_do(lscapi_set_cookie, cookie_table, r->err_headers_out, "Set-Cookie", NULL);

    c->cgi_status = HTTP_UNSET;
    while (1)
    {

        int rv = (*getsfunc) (w, MAX_STRING_LEN - 1, c);
        if (rv == 0)
        {
            if (first_header)
            {
                lscapi_rlog(APLOG_ERR, 0, r, "End of script output before headers: %s",
                            apr_filepath_name_get(r->filename));
                return c->dircfg->err_backend_nohdrs; // HTTP_SERVICE_UNAVAILABLE
            } else
            {
                lscapi_rlog(APLOG_ERR, 0, r, "Premature end of script headers: %s",
                            apr_filepath_name_get(r->filename));
                return c->dircfg->err_backend_endhdrs; // HTTP_SERVICE_UNAVAILABLE
            }
        }
        else if (rv == -1)
        {
            lscapi_rlog(APLOG_ERR, 0, r,
                          "Script timed out before returning headers: %s",
                          apr_filepath_name_get(r->filename));
            return HTTP_GATEWAY_TIME_OUT;
        }

        /* Delete terminal (CR?)LF */

        p = strlen(w);
        if (p > 0 && w[p - 1] == '\n')
        {
            if (p > 1 && w[p - 2] == CR)
            {
                w[p - 2] = '\0';
            }
            else
            {
                w[p - 1] = '\0';
            }
        }

        if (w[0] == '\0')
        {
            if ((c->cgi_status == HTTP_UNSET) && (r->method_number == M_GET))
            {
                int cond_status = ap_meets_conditions(r);
                if (cond_status != OK)
                {
                    c->cgi_status = cond_status;
                    c->status_line = lsapi_get_status_line(c->cgi_status);
                }
            }
            apr_table_overlap(r->err_headers_out, merge,
                APR_OVERLAP_TABLES_MERGE);
            if (apr_is_empty_table(cookie_table) == 0)
            {
                /* the cookies have already been copied to the cookie_table */
                apr_table_unset(r->err_headers_out, "Set-Cookie");
                r->err_headers_out = apr_table_overlay(r->pool,
                    r->err_headers_out, cookie_table);
            }
            if (c->svrcfg->debug_enabled)
            {
                lscapi_rlog(APLOG_NOTICE, 0, r, "lscapi_scan_script_header finishing: cgi_status %d; status_line(%s)", c->cgi_status, c->status_line);
            }
            return OK;
        }

        if (c->svrcfg->debug_enabled)
        {
            lscapi_rlog(APLOG_NOTICE, 0, r, "lscapi_scan_script_header: next line(%s)", w);
        }

        /* if we see a bogus header don't ignore it. Shout and scream */
        left = strchr(w, ':');
        if (left == NULL)
        {
            /* Response status in format "HTTP/x.x nnn ..." is acceptable though */
            unsigned major, minor;
            char code_str[4];
            if (sscanf(w, "HTTP/%1u.%1u %3s", &major, &minor, code_str) == 3)
            {
                c->cgi_status = atoi(code_str);
                c->status_line = apr_pstrdup(r->pool, w+9); // skip "HTTP/1.1 " prefix
                if (c->svrcfg->debug_enabled)
                {
                    lscapi_rlog(APLOG_NOTICE, 0, r, "lscapi_scan_script_header: ROW header found: set cgi_status to %d; and status_line to (%s)", c->cgi_status, c->status_line);
                }
            }
            continue;
        }

        *left++ = '\0';
        while (*left && apr_isspace(*left))
        {
            ++left;
        }

        if (strcasecmp(w, "Content-type") == 0)
        {
            char *tmp;

            /* Nuke trailing whitespace */

            char *endp = left + strlen(left) - 1;
            while (endp > left && apr_isspace(*endp))
            {
                *endp-- = '\0';
            }

            tmp = apr_pstrdup(r->pool, left);
            ap_content_type_tolower(tmp);
            ap_set_content_type(r, tmp);
        }
        /*
         * If the script returned a specific status, that's what
         * we'll use - otherwise we assume 200 OK.
         */
        else if (strcasecmp(w, "Status") == 0)
        {
            c->cgi_status = atoi(left);
            c->status_line = apr_pstrdup(r->pool, left);
            if (c->svrcfg->debug_enabled)
            {
                lscapi_rlog(APLOG_NOTICE, 0, r,
                             "lscapi_scan_script_header: ROW header found: Status header found: set cgi_status to %d; and status_line to (%s)",
                             c->cgi_status, c->status_line);
            }
        }
        else if (strcasecmp(w, "Location") == 0)
        {
            apr_table_set(r->headers_out, w, left);
        }
        else if (strcasecmp(w, "Content-Length") == 0)
        {
            apr_table_set(r->headers_out, w, left);
        }
        else if (strcasecmp(w, "Content-Range") == 0)
        {
            apr_table_set(r->headers_out, w, left);
        }
        else if (strcasecmp(w, "Transfer-Encoding") == 0)
        {
            apr_table_set(r->headers_out, w, left);
        }
        else if (strcasecmp(w, "ETag") == 0)
        {
            apr_table_set(r->headers_out, w, left);
        }
        /*
         * If the script gave us a Last-Modified header, we can't just
         * pass it on blindly because of restrictions on future values.
         */
        else if (strcasecmp(w, "Last-Modified") == 0)
        {
            ap_update_mtime(r, apr_date_parse_http(left));
            ap_set_last_modified(r);
        }
        else if (strcasecmp(w, "Set-Cookie") == 0)
        {
            apr_table_add(cookie_table, w, left);
        }
        else
        {
            apr_table_add(merge, w, left);
        }
        first_header = 0;
    }
    /* never reached - we leave this function within the while loop above */
    return OK;
}

apr_status_t lscapi_do_request(lscapi_rec *lscapi, lsphp_conn_t *backend, request_rec *r,
                               unsigned *eventMaskPtr,
                               lsapi_svr_conf_t *svrcfg, lsapi_dir_conf_t *dircfg)
{
    char errbuf[512];
    int rc;
    lscapi_resp_hdr_t respHdr = { 0 };
/* LSAPI_PRO_ONLY:BEGIN */
    dump_request_info_t *infoPtr;
/* LSAPI_PRO_ONLY:END */

    *eventMaskPtr = 0;
    rc = lscapi_receive_resp_hdr(backend, &respHdr, eventMaskPtr, errbuf, sizeof errbuf);
    if (rc != 0)
    {
        lscapi_rlog(APLOG_ERR, 0, r, "Error receiving response header (lsphp is killed?): %s", errbuf);
        lscapi_set_recoverable_error(lscapi);
        SET_STATUS_WITH_LINE(r, dircfg->err_backend_recvhdr);  //HTTP_SERVICE_UNAVAILABLE
        rc = r->status;
        goto exit_function;
    }
    if (dircfg->measure_time)
    {
        lscapi_write_measured_time(r, LSCAPI_MEASURE_HEADER_GOT);
    }

    char scan_buffer[MAX_STRING_LEN];
    struct hdrs_flags hdrsFlags;
    memset(&hdrsFlags, 0, sizeof(hdrsFlags));

    struct hdrs_context context;

    context.respHdr = &respHdr;
    context.curHdrSize = 0;
    context.curOff = sizeof(lsapi_resp_info) + sizeof(uint16_t) * respHdr.hdrNum;
    context.curNum = 0;
    context.svrcfg = svrcfg;
    context.dircfg = dircfg;
    context.r = r;
    context.hdrsFlagsPtr = &hdrsFlags;
    context.cgi_status = OK;
    context.status_line = NULL;

    int respStatus = respHdr.respStatus;

    if (svrcfg->debug_enabled)
    {
        lscapi_rlog(APLOG_NOTICE, 0, r, "lscapi_do_request: response header received: response status %d", respStatus);
    }

    rc = lscapi_scan_script_header_err_core(r, scan_buffer, getsfunc_HDRBUF, (void *) &context);

    if (rc != OK)
    {
        SET_STATUS_WITH_LINE(r, rc);
        if (svrcfg->debug_enabled)
        {
            lscapi_rlog(APLOG_NOTICE, 0, r, "lscapi_scan_script_header failed: %d: use standard header(%s)", rc, r->status_line);
        }
        read_from_backend(lscapi, backend, r, svrcfg, dircfg, 1, &hdrsFlags, errbuf, sizeof errbuf);  // discard output
        goto exit_function;
    }

/*
            apr_table_setn(r->subprocess_env, "REQUEST_METHOD", r->method);
*/

    if (respStatus != OK)
    {
        if (svrcfg->debug_enabled)
        {
            lscapi_rlog(APLOG_NOTICE, 0, r, "response status is not OK %d", respStatus);
        }
        if (context.cgi_status != HTTP_UNSET)
        {
            r->status = context.cgi_status;
            r->status_line = context.status_line;
            if (svrcfg->debug_enabled)
            {
                lscapi_rlog(APLOG_NOTICE, 0, r, "set status to %d using cgi_status and line(%s)", r->status, r->status_line);
            }
        } else if (respStatus == HTTP_OK)
        {
            if (dircfg->keep_http200)
            {
                r->status = respStatus;
                if (svrcfg->debug_enabled)
                {
                    lscapi_rlog(APLOG_NOTICE, 0, r, "set status to response status 200 due to keep_http200 flag");
                }
                set_status_line(r, svrcfg, &context);
            } else
            {
                if (svrcfg->debug_enabled)
                {
                    lscapi_rlog(APLOG_NOTICE, 0, r, "leave status blank as response status is HTTP_OK: %d", respStatus);
                }
            }
        } else
        {
            r->status = respStatus;
            if (svrcfg->debug_enabled)
            {
                lscapi_rlog(APLOG_NOTICE, 0, r, "set status to response status %d", r->status);
            }
            set_status_line(r, svrcfg, &context);
        }

    } else if (hdrsFlags.www_authenticate_found == 1)
    {
        SET_STATUS_WITH_LINE(r, HTTP_UNAUTHORIZED);
        if (svrcfg->debug_enabled)
        {
            lscapi_rlog(APLOG_NOTICE, 0, r, "set status to %d as WWW-Authenticate header found; standard line (%s)", r->status, r->status_line);
        }
    } else if (context.cgi_status != HTTP_UNSET)
    {
        r->status = context.cgi_status;
        r->status_line = context.status_line;
        if (svrcfg->debug_enabled)
        {
            lscapi_rlog(APLOG_NOTICE, 0, r, "set status to %d using cgi_status and line(%s)", r->status, r->status_line);
        }
    } else
    {
        if (svrcfg->debug_enabled)
        {
            lscapi_rlog(APLOG_NOTICE, 0, r, "nor response code %d, nor cgi_status %d - leave status blank", respStatus, context.cgi_status);
        }
    }

    const char *location = apr_table_get(r->headers_out, "Location");
    if (location && location[0] == '/' && r->status == HTTP_OK)
    {
        read_from_backend(lscapi, backend, r, svrcfg, dircfg, 1, &hdrsFlags, errbuf, sizeof errbuf);  // discard output
        r->method = "GET";
        r->method_number = M_GET;
        apr_table_unset(r->headers_in, "Content-Length");

        ap_internal_redirect_handler(location, r);
        rc = OK;
        goto exit_function;
    }
    else if (location && r->status == HTTP_OK)
    {
        read_from_backend(lscapi, backend, r, svrcfg, dircfg, 1, &hdrsFlags, errbuf, sizeof errbuf);  // discard output
        rc = HTTP_MOVED_TEMPORARILY;
        goto exit_function;
    }

    rc = read_from_backend(lscapi, backend, r, svrcfg, dircfg, 0, &hdrsFlags, errbuf, sizeof errbuf); // send output to browser through filters
    if (rc != 0)
    {
        rc = r->status;
        goto exit_function;
    }
    if (dircfg->measure_time)
    {
        lscapi_write_measured_time(r, LSCAPI_MEASURE_RESPONSE_GOT);
    }

    rc = OK;

exit_function:
/* LSAPI_PRO_ONLY:BEGIN */
    if (dircfg->dump_request_info)
    {
        if (svrcfg->debug_enabled)
        {
            lscapi_rlog(APLOG_NOTICE, 0, r, "before trying to dump request info as dircfg->dump_request_info switched on");
        }
        infoPtr = (dump_request_info_t*)apr_table_get(r->notes, lscapi_request_info_key);
        if (infoPtr)
        {
            infoPtr->http_status_code = r->status;
            gettimeofday(&(infoPtr->get_response_tv), NULL);
            if (svrcfg->debug_enabled)
            {
                lscapi_rlog(APLOG_NOTICE, 0, r, "before call dump_request_info. Response time is %lu.%06lu",
                            (unsigned long)infoPtr->get_response_tv.tv_sec,
                            (unsigned long)infoPtr->get_response_tv.tv_usec );
            }
            lsapi_dump_request_info(r, infoPtr);
        }
        else
        {
            if (svrcfg->debug_enabled)
            {
                lscapi_rlog(APLOG_NOTICE, 0, r, "cannot find %s key in r->notes -> no dump request info",
                            lscapi_request_info_key );
            }
        }
    }
    else
    {
        if (svrcfg->debug_enabled)
        {
            lscapi_rlog(APLOG_NOTICE, 0, r, "will not try to dump request info as dircfg->dump_request_info switched off");
        }
    }
/* LSAPI_PRO_ONLY:END */
    lscapi_free_resp_hdr(backend, &respHdr);

    return rc;
}

void lscapi_reset_http_error_state(request_rec *r)
{
    SET_STATUS_WITH_LINE(r, HTTP_OK);
}


const char LSCAPI_MEASURE_REQUEST_GOT[] = "lscapi-measure-request-got";
const char LSCAPI_MEASURE_CONN_ESTABLISHED[] = "lscapi-measure-conn-established";
const char LSCAPI_MEASURE_REQUEST_SENT[] = "lscapi-measure-request-sent";
const char LSCAPI_MEASURE_HEADER_GOT[] = "lscapi-measure-header-got";
const char LSCAPI_MEASURE_RESPONSE_GOT[] = "lscapi-measure-response-got";

void lscapi_write_measured_time(request_rec *r, const char *key)
{
    struct timeval *tv = apr_palloc(r->pool, sizeof(struct timeval));
    if (tv == NULL)
    {
        return;
    }
    gettimeofday(tv, NULL);
    apr_table_setn(r->notes, key, (char*)tv);
}

apr_status_t lscapi_get_measured_timedelta(request_rec *r,
                                           const char *key_from, const char *key_to, 
                                           struct timeval *tv_delta)
{
    struct timeval *tv_from = (struct timeval *) apr_table_get(r->notes, key_from);
    struct timeval *tv_to = (struct timeval *) apr_table_get(r->notes, key_to);
    if (tv_from == NULL || tv_to == NULL)
    {
        return APR_ENOTIME;
    }
    timersub(tv_to, tv_from, tv_delta);
    return APR_SUCCESS;
}
