/*****************************************************************************
*    Open LiteSpeed is an open source HTTP server.                           *
*    Copyright (C) 2013 - 2015  LiteSpeed Technologies, Inc.                 *
*                                                                            *
*    This program is free software: you can redistribute it and/or modify    *
*    it under the terms of the GNU General Public License as published by    *
*    the Free Software Foundation, either version 3 of the License, or       *
*    (at your option) any later version.                                     *
*                                                                            *
*    This program is distributed in the hope that it will be useful,         *
*    but WITHOUT ANY WARRANTY; without even the implied warranty of          *
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the            *
*    GNU General Public License for more details.                            *
*                                                                            *
*    You should have received a copy of the GNU General Public License       *
*    along with this program. If not, see http://www.gnu.org/licenses/.      *
*****************************************************************************/

#include "../include/ls.h"

#include <lsr/ls_xpool.h>

#include <stdlib.h>
#include <string.h>
#include <openssl/md5.h>

/**
 * Define the module name, MUST BE the same as .so file name;
 * i.e., if MNAME is testmodule, then the .so must be testmodule.so.
 *
 * How to test this sample
 * 1, build it, put the file reqinfhandler.so in $LSWS_HOME/modules
 * 2, in web admin console configuration, enable this module
 * 3, visit http(s)://.../reqinfo(/echo|/md5|/upload) with/without request body
 */

#define MNAME       reqinfhandler
lsi_module_t MNAME;

static char testuri[] = "/reqinfo";

#define CONTENT_HEAD    "<html><head><title>Server request varibles</title></head><body>\r\n"
#define CONTENT_FORMAT  "<tr><td>%s</td><td>%s</td></tr>\r\n"
#define CONTENT_TAIL    "</body></html>\r\n"

typedef struct mydata_s
{
    int type;
    FILE *fp;
    MD5_CTX ctx;
    int req_body_len;           // length in "Content-Length"
    int rcvd_req_body_len;      // recved
    int rcvd_done;              // finished or due to connection error
    int resp_done;
} mydata_t;

// The following matches the index, DO NOT change the order
const char *req_array[] =
{
    "REMOTE_ADDR",
    "REMOTE_PORT",
    "REMOTE_HOST",
    "REMOTE_USER",
    "REMOTE_IDENT",
    "REQUEST_METHOD",
    "QUERY_STRING",
    "AUTH_TYPE",
    "PATH_INFO",
    "SCRIPT_FILENAME",
    "REQUEST_FILENAME",
    "REQUEST_URI",
    "DOCUMENT_ROOT",
    "SERVER_ADMIN",
    "SERVER_NAME",
    "SERVER_ADDR",
    "SERVER_PORT",
    "SERVER_PROTOCOL",
    "SERVER_SOFTWARE",
    "API_VERSION",
    "THE_REQUEST",
    "IS_SUBREQ",
    "TIME",
    "TIME_YEAR",
    "TIME_MON",
    "TIME_DAY",
    "TIME_HOUR",
    "TIME_MIN",
    "TIME_SEC",
    "TIME_WDAY",
    "SCRIPT_NAME",
    "CURRENT_URI",
    "REQUEST_BASENAME",
    "SCRIPT_UID",
    "SCRIPT_GID",
    "SCRIPT_USERNAME",
    "SCRIPT_GROUPNAME",
    "SCRIPT_MODE",
    "SCRIPT_BASENAME",
    "SCRIPT_URI",
    "ORG_REQ_URI",
    "HTTPS",
    "SSL_VERSION",
    "SSL_SESSION_ID",
    "SSL_CIPHER",
    "SSL_CIPHER_USEKEYSIZE",
    "SSL_CIPHER_ALGKEYSIZE",
    "SSL_CLIENT_CERT",
    "GEOIP_ADDR",
    "PATH_TRANSLATED",
};

// The following is used by name value, you can change the order
const char *env_array[] =
{
    "PATH",

    "REMOTE_ADDR",
    "REMOTE_PORT",
    "REMOTE_HOST",
    "REMOTE_USER",
    "REMOTE_IDENT",
    "REQUEST_METHOD",
    "QUERY_STRING",
    "AUTH_TYPE",
    "PATH_INFO",
    "SCRIPT_FILENAME",
    "REQUEST_FILENAME",
    "REQUEST_URI",
    "DOCUMENT_ROOT",
    "SERVER_ADMIN",
    "SERVER_NAME",
    "SERVER_ADDR",
    "SERVER_PORT",
    "SERVER_PROTOCOL",
    "SERVER_SOFTWARE",
    "API_VERSION",
    "THE_REQUEST",
    "IS_SUBREQ",
    "TIME",
    "TIME_YEAR",
    "TIME_MON",
    "TIME_DAY",
    "TIME_HOUR",
    "TIME_MIN",
    "TIME_SEC",
    "TIME_WDAY",
    "SCRIPT_NAME",
    "CURRENT_URI",
    "REQUEST_BASENAME",
    "SCRIPT_UID",
    "SCRIPT_GID",
    "SCRIPT_USERNAME",
    "SCRIPT_GROUPNAME",
    "SCRIPT_MODE",
    "SCRIPT_BASENAME",
    "SCRIPT_URI",
    "ORG_REQ_URI",
    "HTTPS",
    "SSL_VERSION",
    "SSL_SESSION_ID",
    "SSL_CIPHER",
    "SSL_CIPHER_USEKEYSIZE",
    "SSL_CIPHER_ALGKEYSIZE",
    "SSL_CLIENT_CERT",
    "GEOIP_ADDR",
    "PATH_TRANSLATED",
};

const char *reqhdr_array[] =
{
    "Accept",
    "Accept-Charset",
    "Accept-Encoding",
    "Accept-Language",
    "Accept-Datetime",
    "Authorization",
    "Cache-Control",
    "Connection",
    "Content-Type",
    "Content-Length",
    "Content-MD5",
    "Cookie",
    "Date",
    "Expect",
    "From",
    "Host",
    "Pragma",
    "Proxy-Authorization"
    "If-Match",
    "If-Modified-Since",
    "If-None-Match",
    "If-Range",
    "If-Unmodified-Since",
    "Max-Forwards",
    "Origin",
    "Referer",
    "User-Agent",
    "Range",
    "Upgrade",
    "Via",
    "X-Forwarded-For",
};


static int free_mydata(void *data)
{
    mydata_t *mydata = (mydata_t *)data;
    if (mydata != NULL)
    {
        // free (mydata); Do not need to free with session pool,
        //   will be freed at the end of the session.
        g_api->log(NULL, LSI_LOG_DEBUG, "#### reqinfomodule free_mydata\n");
    }
    return 0;
}


static int initdata(lsi_param_t *param)
{
    mydata_t *mydata = (mydata_t *)g_api->get_module_data(param->session,
                       &MNAME, LSI_DATA_HTTP);
    ls_xpool_t *pPool = g_api->get_session_pool(param->session);
    mydata = (mydata_t *)ls_xpool_alloc(pPool, sizeof(mydata_t));
    memset(mydata, 0, sizeof(mydata_t));
    g_api->log(NULL, LSI_LOG_DEBUG, "#### reqinfomodule initdata\n");
    g_api->set_module_data(param->session, &MNAME, LSI_DATA_HTTP,
                           (void *)mydata);
    return 0;
}


// 0:no body or no deal, 1,echo, 2: md5, 3, save to file
static int get_reqbody_dealertype(const lsi_session_t *session)
{
    char path[512];
    int n;
    if (g_api->get_req_content_length(session) > 0)
    {
        n = g_api->get_req_var_by_id(session, LSI_VAR_PATH_INFO, path, 512);
        if (n >= 5 && strncasecmp(path, "/echo", 5) == 0)
            return 1;
        else if (n >= 4 && strncasecmp(path, "/md5", 4) == 0)
            return 2;
        else if (n >= 7 && strncasecmp(path, "/upload", 7) == 0)
            return 3;
    }

    // All other cases, no deal
    return 0;
}


static inline void append(const lsi_session_t *session, const char *s, int n)
{
    if (n == 0)
        n = strlen(s);
    g_api->append_resp_body(session, (char *)s, n);
}


static int on_read(const lsi_session_t *session)
{
    unsigned char md5[16];
    char buf[8192];
    int ret, i;
    int readbytes = 0, written = 0;
    mydata_t *mydata = (mydata_t *)g_api->get_module_data(session, &MNAME,
                       LSI_DATA_HTTP);
    if (mydata == NULL || mydata->type == 0)
    {
        g_api->end_resp(session);
        return 0;
    }

    while ((ret = g_api->read_req_body(session, buf, sizeof(buf))) > 0)
    {
        mydata->rcvd_req_body_len += ret;
        readbytes += ret;

        if (mydata->type == 1)
        {
            append(session, buf, ret);
            written += ret;
        }
        else if (mydata->type == 2)
            MD5_Update(&mydata->ctx, buf, ret);
        else
            fwrite(buf, 1, ret, mydata->fp);

        if (mydata->rcvd_req_body_len >= mydata->req_body_len)
        {
            mydata->rcvd_done = 1;
            break;
        }
    }
    if (ret == 0)
        mydata->rcvd_done = 1;

    if (mydata->rcvd_done == 1)
    {
        if (mydata->type == 2)
        {
            MD5_Final(md5, &mydata->ctx);
            for (i = 0; i < 16; ++i)
                sprintf(buf + i * 2, "%02X", md5[i]);
            append(session, "MD5 is<br>", 10);
            append(session, buf, 32);
            written += 42;
        }
        else if (mydata->type == 3)
        {
            if (mydata->fp != NULL)
            {
                fclose(mydata->fp);
                mydata->fp = NULL;
                append(session, "File uploaded.<br>", 18);
                written += 18;
            }
        }
        mydata->resp_done = 1;
    }

    if (written > 0)
        g_api->flush(session);
    g_api->set_handler_write_state(session, 1);
    //g_api->end_resp(session);
    return readbytes;
}


static int begin_process(const lsi_session_t *session)
{
#define VALMAXSIZE 4096
#define LINEMAXSIZE (VALMAXSIZE + 50)
    char val[VALMAXSIZE], line[LINEMAXSIZE] = {0};
    int n;
    int i;
    const char *p;
    char *buf;
    mydata_t *mydata = (mydata_t *)g_api->get_module_data(session, &MNAME,
                       LSI_DATA_HTTP);
    ls_xpool_t *pPool = g_api->get_session_pool(session);

    //Create response body
    append(session, CONTENT_HEAD, 0);

    //Original request header
    n = g_api->get_req_raw_headers_length(session);
    buf = (char *)ls_xpool_alloc(pPool, n + 1);
    memset(buf, 0, n + 1);
    n = g_api->get_req_raw_headers(session, buf, n + 1);
    append(session, "Original request<table border=1><tr><td><pre>\r\n", 0);
    append(session, buf, n);
    append(session, "\r\n</pre></td></tr>\r\n", 0);
    ls_xpool_free(pPool, buf);

    append(session, "\r\n</table><br>Request headers<br><table border=1>\r\n",
           0);
    for (i = 0; i < sizeof(reqhdr_array) / sizeof(char *); ++i)
    {
        p = g_api->get_req_header(session, reqhdr_array[i],
                                  strlen(reqhdr_array[i]), &n);
        if ((p != NULL) && p[0] != 0 && n > 0)
        {
            memcpy(val, p, n);
            val[n] = '\0';
            n = snprintf(line, LINEMAXSIZE - 1, CONTENT_FORMAT, reqhdr_array[i],
                         val);
            append(session, line, n);
        }
    }


    append(session,
           "\r\n</table><br>Server req env<br><table border=1>\r\n", 0);
    //Server req env
    for (i = LSI_VAR_REMOTE_ADDR; i < LSI_VAR_COUNT; ++i)
    {
        n = g_api->get_req_var_by_id(session, i, val, VALMAXSIZE);
        if (n > 0)
        {
            val[n] = '\0';
            n = snprintf(line, LINEMAXSIZE - 1, CONTENT_FORMAT, req_array[i],
                         val);
            append(session, line, n);
        }
    }

    append(session, "\r\n</table><br>env varibles<br><table border=1>\r\n", 0);
    for (i = 0; i < sizeof(env_array) / sizeof(char *); ++i)
    {
        //env varibles
        n = g_api->get_req_env(session, env_array[i], strlen(env_array[i]), val,
                               VALMAXSIZE);
        if (n > 0)
        {
            val[n] = '\0';
            n = snprintf(line, LINEMAXSIZE - 1, CONTENT_FORMAT, env_array[i],
                         val);
            append(session, line, n);
        }
    }


    p = g_api->get_req_cookies(session, &n);
    if ((p != NULL) && p[0] != 0 && n > 0)
    {
        append(session,
               "\r\n</table><br>Request cookies<br><table border=1>\r\n", 0);
        append(session, "<tr><td>Cookie</td><td>", 0);
        append(session, p, n);
        append(session, "</td></tr>", 0);
    }

    n = g_api->get_req_cookie_count(session);
    if (n > 0)
    {
        //try get a certen cookie
        p = g_api->get_cookie_value(session, "LSWSWEBUI", 9, &n);
        if ((p != NULL) && n > 0)
        {
            append(session, "<tr><td>cookie_LSWSWEBUI</td><td>", 0);
            append(session, p, n);
            append(session, "</td></tr>", 0);
        }
    }
    append(session, "</table>", 0);

    n = get_reqbody_dealertype(session);

    mydata->req_body_len = g_api->get_req_content_length(session);
    mydata->rcvd_req_body_len = 0;
    mydata->type = n;
    sprintf(line,
            "Will deal with the req body.Type = %d, req body lenghth = %d<br>",
            n, mydata->req_body_len);

    append(session, line, 0);
    if (mydata->type == 0)
    {
        append(session, CONTENT_TAIL, 0);
        mydata->rcvd_done = 1;
        mydata->resp_done = 1;
    }

    g_api->set_status_code(session, 200);

    if (mydata->type == 3)  // Save to file
        mydata->fp = fopen("/tmp/uploadfile", "wb");
    else if (mydata->type == 2)   // Md5
        MD5_Init(&mydata->ctx);

    g_api->flush(session);

//     if ( mydata->type != 0)
    on_read(session);

    //g_api->end_resp(session);
    return 0;
}


static int on_write(const lsi_session_t *session)
{
    mydata_t *mydata = (mydata_t *)g_api->get_module_data(session, &MNAME,
                       LSI_DATA_HTTP);
    return (mydata == NULL || mydata->type == 0 || mydata->resp_done == 1) ?
           LSI_RSP_DONE : LSI_RSP_MORE;
}


static int rcvd_req_header_cbf(lsi_param_t *param)
{
    const char *uri;
    int len;
    uri = g_api->get_req_uri(param->session, &len);
    if ((uri != NULL) && (len >= sizeof(testuri) - 1)
        && (strncasecmp(uri, testuri, sizeof(testuri) - 1) == 0))
        g_api->register_req_handler(param->session, &MNAME, sizeof(testuri) - 1);
    return LSI_OK;
}


static int init_module(lsi_module_t *module)
{
    g_api->init_module_data(module, free_mydata, LSI_DATA_HTTP);
    return 0;
}


static int clean_up(const lsi_session_t *session)
{
    g_api->free_module_data(session, &MNAME, LSI_DATA_HTTP,
                            free_mydata);
    return 0;
}


static lsi_serverhook_t server_hooks[] =
{
    { LSI_HKPT_HTTP_BEGIN, initdata, LSI_HOOK_NORMAL, LSI_FLAG_ENABLED },
    //{ LSI_HKPT_HTTP_END, resetdata, LSI_HOOK_NORMAL, LSI_FLAG_ENABLED },
    { LSI_HKPT_RCVD_REQ_HEADER, rcvd_req_header_cbf, LSI_HOOK_NORMAL, LSI_FLAG_ENABLED },
    LSI_HOOK_END   //Must put this at the end position
};

static lsi_reqhdlr_t myhandler = { begin_process, on_read, on_write, clean_up, NULL, NULL, NULL};

LSMODULE_EXPORT lsi_module_t MNAME =
{
    LSI_MODULE_SIGNATURE, init_module, &myhandler, NULL, "", server_hooks, {0}
};

