/* --- BEGIN COPYRIGHT BLOCK ---
 * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
 * Copyright (C) 2005 Red Hat, Inc.
 * All rights reserved.
 *
 * License: GPL (version 3 or any later version).
 * See LICENSE for details.
 * --- END COPYRIGHT BLOCK --- */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif


/**
 * Implementation of a Simple HTTP Client
 */
#include <stdio.h>
#include <string.h>

#include "nspr.h"
#include "nss.h"
#include "pk11func.h"
#include "ssl.h"
#include "prprf.h"
#include "plstr.h"
#include "slapi-plugin.h"
#include "http_client.h"
#include "secerr.h"
#include "sslerr.h"
#include "slap.h"
#include "slapi-private.h"
#include "slapi-plugin-compat4.h"
#include <sys/stat.h>

#ifdef __cplusplus
extern "C" {
#endif

#ifdef BUILD_STANDALONE
#define slapi_log_err(a, b, c, d) printf((c), (d))
#define stricmp strcasecmp
#endif

#ifdef __cplusplus
}
#endif

#define HTTP_PLUGIN_SUBSYSTEM "http-client-plugin" /* used for logging */

#define HTTP_IMPL_SUCCESS 0
#define HTTP_IMPL_FAILURE -1

#define HTTP_REQ_TYPE_GET 1
#define HTTP_REQ_TYPE_REDIRECT 2
#define HTTP_REQ_TYPE_POST 3

#define HTTP_GET "GET"
#define HTTP_POST "POST"
#define HTTP_PROTOCOL "HTTP/1.0"
#define HTTP_CONTENT_LENGTH "Content-length:"
#define HTTP_CONTENT_TYPE_URL_ENCODED "Content-type: application/x-www-form-urlencoded"
#define HTTP_GET_STD_LEN 18
#define HTTP_POST_STD_LEN 85
#define HTTP_DEFAULT_BUFFER_SIZE 4096
#define HTTP_RESPONSE_REDIRECT (retcode == 302 || retcode == 301)

/**
 * Error strings used for logging error messages
 */
#define HTTP_ERROR_BAD_URL " Badly formatted URL"
#define HTTP_ERROR_NET_ADDR " NetAddr initialization failed"
#define HTTP_ERROR_SOCKET_CREATE " Creation of socket failed"
#define HTTP_ERROR_SSLSOCKET_CREATE " Creation of SSL socket failed"
#define HTTP_ERROR_CONNECT_FAILED " Couldn't connect to remote host"
#define HTTP_ERROR_SEND_REQ " Send request failed"
#define HTTP_ERROR_BAD_RESPONSE " Invalid response from remote host"

#define HTTP_PLUGIN_DN "cn=HTTP Client,cn=plugins,cn=config"
#define CONFIG_DN "cn=config"
#define ATTR_CONNECTION_TIME_OUT "nsHTTPConnectionTimeOut"
#define ATTR_READ_TIME_OUT "nsHTTPReadTimeOut"
#define ATTR_RETRY_COUNT "nsHTTPRetryCount"
#define ATTR_DS_SECURITY "nsslapd-security"
#define ATTR_INSTANCE_PATH "nsslapd-errorlog"

/*static Slapi_ComponentId *plugin_id = NULL;*/

typedef struct
{
    int retryCount;
    int connectionTimeOut;
    int readTimeOut;
    int nssInitialized;
    char *DS_sslOn;
} httpPluginConfig;

httpPluginConfig *httpConfig;

/**
 * Public functions
 */
int http_impl_init(Slapi_ComponentId *plugin_id);
int http_impl_get_text(char *url, char **data, int *bytesRead);
int http_impl_get_binary(char *url, char **data, int *bytesRead);
int http_impl_get_redirected_uri(char *url, char **data, int *bytesRead);
int http_impl_post(char *url, httpheader **httpheaderArray, char *body, char **data, int *bytesRead);
void http_impl_shutdown(void);

/**
 * Http handling functions
 */
static int doRequest(const char *url, httpheader **httpheaderArray, char *body, char **buf, int *bytesRead, int reqType);
static int doRequestRetry(const char *url, httpheader **httpheaderArray, char *body, char **buf, int *bytesRead, int reqType);
static void setTCPNoDelay(PRFileDesc *fd);
static PRStatus sendGetReq(PRFileDesc *fd, const char *path);
static PRStatus sendPostReq(PRFileDesc *fd, const char *path, httpheader **httpheaderArray, char *body);
static PRStatus processResponse(PRFileDesc *fd, char **resBUF, int *bytesRead, int reqType);
static PRStatus getChar(PRFileDesc *fd, char *buf);
static PRInt32 http_read(PRFileDesc *fd, char *buf, int size);
static PRStatus getBody(PRFileDesc *fd, char **buf, int *actualBytesRead);
static PRBool isWhiteSpace(char ch);
static PRStatus sendFullData(PRFileDesc *fd, char *buf, int timeOut);

/**
 * Helper functions to parse URL
 */
static PRStatus parseURI(const char *url, char **host, PRInt32 *port, char **path, int *sslOn);
static void toLowerCase(char *str);
static PRStatus parseAtPort(const char *url, PRInt32 *port, char **path);
static PRStatus parseAtPath(const char *url, char **path);
static PRInt32 getPort(const char *src);
static PRBool isAsciiSpace(char aChar);
static PRBool isAsciiDigit(char aChar);
static char *isHttpReq(const char *url, int *sslOn);

/*To get config from entry*/
static int readConfigLDAPurl(Slapi_ComponentId *plugin_id, char *plugindn);
static int parseHTTPConfigEntry(Slapi_Entry *e);
static int parseConfigEntry(Slapi_Entry *e);

/*SSL functions */
PRFileDesc *setupSSLSocket(PRFileDesc *fd);

/*SSL callback functions */
SECStatus badCertHandler(void *arg, PRFileDesc *socket);
SECStatus authCertificate(void *arg, PRFileDesc *socket, PRBool checksig, PRBool isServer);
SECStatus getClientAuthData(void *arg, PRFileDesc *socket, struct CERTDistNamesStr *caNames, struct CERTCertificateStr **pRetCert, struct SECKEYPrivateKeyStr **pRetKey);
SECStatus handshakeCallback(PRFileDesc *socket, void *arg);

static int
doRequestRetry(const char *url, httpheader **httpheaderArray, char *body, char **buf, int *bytesRead, int reqType)
{
    int status = HTTP_IMPL_SUCCESS;
    int retrycnt = 0;
    int i = 1;

    retrycnt = httpConfig->retryCount;

    if (retrycnt == 0) {
        slapi_log_err(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, "doRequestRetry - Retry Count cannot be read. Setting to default value of 3\n");
        retrycnt = 3;
    }
    status = doRequest(url, httpheaderArray, body, buf, bytesRead, reqType);
    if (status != HTTP_IMPL_SUCCESS) {
        slapi_log_err(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, "doRequestRetry - Failed to perform http request \n");
        while (retrycnt > 0) {
            slapi_log_err(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, "doRequestRetry - Retrying http request %d.\n", i);
            status = doRequest(url, httpheaderArray, body, buf, bytesRead, reqType);
            if (status == HTTP_IMPL_SUCCESS) {
                break;
            }
            retrycnt--;
            i++;
        }
        if (status != HTTP_IMPL_SUCCESS) {
            slapi_log_err(SLAPI_LOG_ERR, HTTP_PLUGIN_SUBSYSTEM, "doRequestRetry - Failed to perform http request after %d attempts.\n", i);
            slapi_log_err(SLAPI_LOG_ERR, HTTP_PLUGIN_SUBSYSTEM, "doRequestRetry - Verify plugin URI configuration and contact Directory Administrator.\n");
        }
    }
    return status;
}

static int
doRequest(const char *url, httpheader **httpheaderArray, char *body, char **buf, int *bytesRead, int reqType)
{
    PRStatus status = PR_SUCCESS;

    char *host = NULL;
    char *path = NULL;
    PRFileDesc *fd = NULL;
    PRNetAddr addr;
    PRInt32 port;
    PRInt32 errcode = 0;
    PRInt32 http_connection_time_out = 0;
    PRInt32 sslOn = 0;

    slapi_log_err(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, "doRequest - BEGIN\n");
    slapi_log_err(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, "doRequest - url=[%s] \n", url);

    /* Parse the URL and initialize the host, port, path */
    if (parseURI(url, &host, &port, &path, &sslOn) == PR_FAILURE) {
        slapi_log_err(SLAPI_LOG_ERR, HTTP_PLUGIN_SUBSYSTEM,
                      "doRequest: %s \n", HTTP_ERROR_BAD_URL);
        status = PR_FAILURE;
        goto bail;
    }

    slapi_log_err(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, "doRequest - host=[%s] port[%d] path[%s] \n", host, port, path);

    /* Initialize the Net Addr */
    if (PR_StringToNetAddr(host, &addr) == PR_FAILURE) {
        char buf[PR_NETDB_BUF_SIZE];
        PRHostEnt ent;

        status = PR_GetIPNodeByName(host, PR_AF_INET, PR_AI_DEFAULT, buf, sizeof(buf), &ent);
        if (status == PR_SUCCESS) {
            PR_EnumerateHostEnt(0, &ent, (PRUint16)port, &addr);
        } else {
            slapi_log_err(SLAPI_LOG_ERR, HTTP_PLUGIN_SUBSYSTEM,
                          "doRequest - %s\n", HTTP_ERROR_NET_ADDR);
            status = HTTP_CLIENT_ERROR_NET_ADDR;
            goto bail;
        }
    } else {
        addr.inet.port = (PRUint16)port;
    }

    slapi_log_err(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, "doRequest - Successfully created NetAddr \n");

    /* open a TCP connection to the server */
    fd = PR_NewTCPSocket();
    if (!fd) {
        slapi_log_err(SLAPI_LOG_ERR, HTTP_PLUGIN_SUBSYSTEM,
                      "doRequest - %s\n", HTTP_ERROR_SOCKET_CREATE);
        status = HTTP_CLIENT_ERROR_SOCKET_CREATE;
        goto bail;
    }

    slapi_log_err(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, "doRequest - Successfully created New TCP Socket \n");

    /* immediately send the response */
    setTCPNoDelay(fd);

    if (sslOn) {
        fd = setupSSLSocket(fd);
        if (fd == NULL) {
            slapi_log_err(SLAPI_LOG_ERR, HTTP_PLUGIN_SUBSYSTEM,
                          "doRequest - %s\n", HTTP_ERROR_SSLSOCKET_CREATE);
            status = HTTP_CLIENT_ERROR_SSLSOCKET_CREATE;
            goto bail;
        }

        if (SSL_SetURL(fd, host) != 0) {
            errcode = PR_GetError();
            slapi_log_err(SLAPI_LOG_ERR, HTTP_PLUGIN_SUBSYSTEM,
                          "doRequest - SSL_SetURL -> NSPR Error code (%d) \n", errcode);
            status = HTTP_CLIENT_ERROR_SSLSOCKET_CREATE;
            goto bail;
        }
    }

    http_connection_time_out = httpConfig->connectionTimeOut;
    /* connect to the host */
    if (PR_Connect(fd, &addr, PR_MillisecondsToInterval(http_connection_time_out)) == PR_FAILURE) {
        errcode = PR_GetError();
        slapi_log_err(SLAPI_LOG_ERR, HTTP_PLUGIN_SUBSYSTEM,
                      "doRequest - %s (%s:%d) -> NSPR Error code (%d)\n",
                      HTTP_ERROR_CONNECT_FAILED, host, addr.inet.port, errcode);
        status = HTTP_CLIENT_ERROR_CONNECT_FAILED;
        goto bail;
    }

    slapi_log_err(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, "doRequest - Successfully connected to host [%s] \n", host);

    /* send the request to the server */
    if (reqType == HTTP_REQ_TYPE_POST) {
        if (sendPostReq(fd, path, httpheaderArray, body) == PR_FAILURE) {
            slapi_log_err(SLAPI_LOG_ERR, HTTP_PLUGIN_SUBSYSTEM,
                          "doRequest - sendPostReq: %s (%s)\n", HTTP_ERROR_SEND_REQ, path);
            status = HTTP_CLIENT_ERROR_SEND_REQ;
            goto bail;
        }
    } else {
        if (sendGetReq(fd, path) == PR_FAILURE) {
            slapi_log_err(SLAPI_LOG_ERR, HTTP_PLUGIN_SUBSYSTEM,
                          "doRequest - sendGetReq: %s (%s)\n", HTTP_ERROR_SEND_REQ, path);
            status = HTTP_CLIENT_ERROR_SEND_REQ;
            goto bail;
        }
    }

    slapi_log_err(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, "doRequest - Successfully sent the request [%s] \n", path);

    /* read the response */
    if (processResponse(fd, buf, bytesRead, reqType) == PR_FAILURE) {
        slapi_log_err(SLAPI_LOG_ERR, HTTP_PLUGIN_SUBSYSTEM,
                      "doRequest - %s (%s)\n", HTTP_ERROR_BAD_RESPONSE, url);
        status = HTTP_CLIENT_ERROR_BAD_RESPONSE;
        goto bail;
    }
    slapi_log_err(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, "doRequest - Successfully read the response\n");
bail:
    if (host) {
        PR_Free(host);
    }
    if (path) {
        PR_Free(path);
    }
    if (fd) {
        PR_Close(fd);
        fd = NULL;
    }
    slapi_log_err(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, "doRequest - END\n");
    return status;
}

static PRStatus
processResponse(PRFileDesc *fd, char **resBUF, int *bytesRead, int reqType)
{
    PRStatus status = PR_SUCCESS;
    char *location = NULL;
    char *protocol = NULL;
    char *statusNum = NULL;
    char *statusString = NULL;
    char *headers = NULL;

    char tmp[HTTP_DEFAULT_BUFFER_SIZE];
    int pos = 0;
    char ch;
    int index;
    int retcode;

    PRBool doneParsing = PR_FALSE;
    PRBool isRedirect = PR_FALSE;
    char name[HTTP_DEFAULT_BUFFER_SIZE];
    char value[HTTP_DEFAULT_BUFFER_SIZE];
    PRBool atEOL = PR_FALSE;
    PRBool inName = PR_TRUE;

    /* PKBxxx: If we are getting a redirect and the response is more the
     * the HTTP_DEFAULT_BUFFER_SIZE, it will cause the server to crash. A 4k
     * buffer should be good enough.
     */
    slapi_log_err(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, "processResponse - BEGIN\n");

    headers = (char *)PR_Calloc(1, 4 * HTTP_DEFAULT_BUFFER_SIZE);
    /* Get protocol string */
    index = 0;
    while (1) {
        status = getChar(fd, headers + pos);
        if (status == PR_FAILURE) {
            /* Error : */
            goto bail;
        }
        ch = (char)headers[pos];
        pos++;
        if (!isWhiteSpace(ch)) {
            tmp[index++] = ch;
        } else {
            break;
        }
    }
    tmp[index] = '\0';
    protocol = PL_strdup(tmp);

    slapi_log_err(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, "processResponse - protocol=[%s] \n", protocol);

    /* Get status num */
    index = 0;
    while (1) {
        status = getChar(fd, headers + pos);
        if (status == PR_FAILURE) {
            /* Error : */
            goto bail;
        }
        ch = (char)headers[pos];
        pos++;
        if (!isWhiteSpace(ch)) {
            tmp[index++] = ch;
        } else {
            break;
        }
    }
    tmp[index] = '\0';
    statusNum = PL_strdup(tmp);
    retcode = atoi(tmp);

    slapi_log_err(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, "processResponse - statusNum=[%s] \n", statusNum);

    if (HTTP_RESPONSE_REDIRECT && (reqType == HTTP_REQ_TYPE_REDIRECT)) {
        isRedirect = PR_TRUE;
    }
    /* Get status string */
    if (ch != '\r') {
        index = 0;
        while (ch != '\r') {
            status = getChar(fd, headers + pos);
            if (status == PR_FAILURE) {
                /* Error : */
                goto bail;
            }
            ch = (char)headers[pos];
            pos++;
            tmp[index++] = ch;
        }
        tmp[index] = '\0';
        statusString = PL_strdup(tmp);
        slapi_log_err(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, "processResponse - statusString [%s] \n", statusString);
    }

    /**
     * Skip CRLF
     */
    status = getChar(fd, headers + pos);
    if (status == PR_FAILURE) {
        /* Error : */
        goto bail;
    }
    ch = (char)headers[pos];
    pos++;

    /**
     * loop over response headers
     */
    index = 0;
    while (!doneParsing) {
        status = getChar(fd, headers + pos);
        if (status == PR_FAILURE) {
            /* Error : */
            goto bail;
        }
        ch = (char)headers[pos];
        pos++;
        switch (ch) {
        case ':':
            if (inName) {
                name[index] = '\0';
                index = 0;
                inName = PR_FALSE;

                /* skip whitespace */
                ch = ' ';

                /*  status = getChar(fd, headers+pos);
                  if (status == PR_FAILURE) {
                      goto bail;
                  }
                  ch = (char)headers[pos];
                  pos++; */

                while (isWhiteSpace(ch)) {
                    status = getChar(fd, headers + pos);
                    if (status == PR_FAILURE) {
                        /* Error : */
                        goto bail;
                    }
                    ch = (char)headers[pos];
                    pos++;
                }
                value[index++] = ch;
            } else {
                value[index++] = ch;
            }
            break;
        case '\r':
            if (inName && !atEOL) {
                return PR_FALSE;
            }
            break;
        case '\n':
            if (atEOL) {
                doneParsing = PR_TRUE;
                break;
            }
            if (inName) {
                return PR_FALSE;
            }
            value[index] = '\0';
            index = 0;
            inName = PR_TRUE;
            slapi_log_err(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, "processResponse - name=[%s] value=[%s]\n", name, value);
            if (isRedirect && !PL_strcasecmp(name, "location")) {
                location = PL_strdup(value);
            }
            atEOL = PR_TRUE;
            break;
        default:
            atEOL = PR_FALSE;
            if (inName) {
                name[index++] = ch;
            } else {
                value[index++] = ch;
            }
            break;
        }
    }

    if (!isRedirect) {
        getBody(fd, resBUF, bytesRead);
    } else {
        *resBUF = PL_strdup(location);
        *bytesRead = strlen(location);
    }
    slapi_log_err(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, "processResponse -Response Buffer=[%s] bytesRead=[%d] \n", *resBUF, *bytesRead);

bail:

    if (headers) {
        PR_Free(headers);
    }
    if (protocol) {
        PL_strfree(protocol);
    }
    if (statusNum) {
        PL_strfree(statusNum);
    }
    if (statusString) {
        PL_strfree(statusString);
    }
    if (location) {
        PL_strfree(location);
    }

    slapi_log_err(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, "processResponse - END\n");
    return status;
}

static PRStatus
sendGetReq(PRFileDesc *fd, const char *path)
{
    PRStatus status = PR_SUCCESS;
    char *reqBUF = NULL;
    PRInt32 http_connection_time_out = 0;
    int buflen = (HTTP_GET_STD_LEN + strlen(path));

    reqBUF = (char *)PR_Calloc(1, buflen);
    if (!reqBUF) {
        status = PR_FAILURE;
        goto out;
    }

    strcpy(reqBUF, HTTP_GET);
    strcat(reqBUF, " ");
    strcat(reqBUF, path);
    strcat(reqBUF, " ");
    strcat(reqBUF, HTTP_PROTOCOL);
    strcat(reqBUF, "\r\n\r\n\0");

    http_connection_time_out = httpConfig->connectionTimeOut;
    status = sendFullData(fd, reqBUF, http_connection_time_out);
out:
    if (reqBUF) {
        PR_Free(reqBUF);
        reqBUF = 0;
    }
    return status;
}

static PRStatus
sendFullData(PRFileDesc *fd, char *buf, int timeOut)
{
    int dataSent = 0;
    int bufLen = strlen(buf);
    int retVal = 0;
    PRInt32 errcode = 0;
    while (dataSent < bufLen) {
        retVal = PR_Send(fd, buf + dataSent, bufLen - dataSent, 0, PR_MillisecondsToInterval(timeOut));
        if (retVal == -1)
            break;
        dataSent += retVal;
    }
    if (dataSent == bufLen)
        return PR_SUCCESS;
    else {
        errcode = PR_GetError();
        slapi_log_err(SLAPI_LOG_ERR, HTTP_PLUGIN_SUBSYSTEM,
                      "sendFullData - dataSent=%d bufLen=%d -> NSPR Error code (%d)\n",
                      dataSent, bufLen, errcode);
        slapi_log_err(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, "sendFullData - NSPR Error code (%d) \n", errcode);
        return PR_FAILURE;
    }
}

static PRStatus
sendPostReq(PRFileDesc *fd, const char *path, httpheader **httpheaderArray, char *body)
{
    PRStatus status = PR_SUCCESS;
    char body_len_str[20];
    char *reqBUF = NULL;
    PRInt32 http_connection_time_out = 0;
    int i = 0;
    int body_len, buflen = 0;
    int path_len;

    if (body) {
        body_len = strlen(body);
    } else {
        body_len = 0;
    }
    if (path) {
        path_len = strlen(path);
    } else {
        path = "";
        path_len = 0;
    }

    PR_snprintf(body_len_str, sizeof(body_len_str), "%d", body_len);

    buflen = (HTTP_POST_STD_LEN + path_len + body_len + strlen(body_len_str));

    for (i = 0; httpheaderArray[i] != NULL; i++) {

        if (httpheaderArray[i]->name != NULL) {
            buflen += strlen(httpheaderArray[i]->name) + 2;
            if (httpheaderArray[i]->value != NULL)
                buflen += strlen(httpheaderArray[i]->value) + 2;
        }
    }

    reqBUF = (char *)PR_Calloc(1, buflen);
    if (!reqBUF) {
        status = PR_FAILURE;
        goto out;
    }

    strcpy(reqBUF, HTTP_POST);
    strcat(reqBUF, " ");
    strcat(reqBUF, path);
    strcat(reqBUF, " ");
    strcat(reqBUF, HTTP_PROTOCOL);
    strcat(reqBUF, "\r\n");
    strcat(reqBUF, HTTP_CONTENT_LENGTH);
    strcat(reqBUF, " ");
    strcat(reqBUF, body_len_str);
    strcat(reqBUF, "\r\n");
    strcat(reqBUF, HTTP_CONTENT_TYPE_URL_ENCODED);
    strcat(reqBUF, "\r\n");

    for (i = 0; httpheaderArray[i] != NULL; i++) {

        if (httpheaderArray[i]->name != NULL)
            strcat(reqBUF, httpheaderArray[i]->name);
        strcat(reqBUF, ": ");
        if (httpheaderArray[i]->value != NULL)
            strcat(reqBUF, httpheaderArray[i]->value);
        strcat(reqBUF, "\r\n");
    }

    strcat(reqBUF, "\r\n");
    if (body) {
        strcat(reqBUF, body);
    }
    strcat(reqBUF, "\0");

    slapi_log_err(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, "sendPostReq - reqBUF is %s \n", reqBUF);
    http_connection_time_out = httpConfig->connectionTimeOut;

    status = sendFullData(fd, reqBUF, http_connection_time_out);
out:
    if (reqBUF) {
        PR_Free(reqBUF);
        reqBUF = 0;
    }
    return status;
}


static PRStatus
getChar(PRFileDesc *fd, char *buf)
{
    PRInt32 bytesRead = http_read(fd, buf, 1);
    if (bytesRead <= 0) {
        PRInt32 errcode = PR_GetError();
        slapi_log_err(SLAPI_LOG_ERR, HTTP_PLUGIN_SUBSYSTEM, "sendPostReq - "
                                                            "getChar - NSPR Error code (%d)\n",
                      errcode);
        return PR_FAILURE;
    }
    return PR_SUCCESS;
}

static PRStatus
getBody(PRFileDesc *fd, char **buf, int *actualBytesRead)
{
    int totalBytesRead = 0;
    int size = 4 * HTTP_DEFAULT_BUFFER_SIZE;
    int bytesRead = size;
    char *data = (char *)PR_Calloc(1, size);
    while (bytesRead == size) {
        bytesRead = http_read(fd, (data + totalBytesRead), size);
        if (bytesRead <= 0) {
            /* Read error */
            return PR_FAILURE;
        }
        if (bytesRead == size) {
            /* more data to be read so increase the buffer */
            size = size * 2;
            data = (char *)PR_Realloc(data, size);
        }
        totalBytesRead += bytesRead;
    }
    *buf = data;
    *actualBytesRead = totalBytesRead;

    return PR_SUCCESS;
}

static PRInt32
http_read(PRFileDesc *fd, char *buf, int size)
{
    PRInt32 http_read_time_out = 0;
    http_read_time_out = httpConfig->readTimeOut;
    return PR_Recv(fd, buf, size, 0, PR_MillisecondsToInterval(http_read_time_out));
}

static PRBool
isWhiteSpace(char ch)
{
    PRBool b = PR_FALSE;
    if (ch == ' ') {
        b = PR_TRUE;
    }
    return b;
}

static PRStatus
parseURI(const char *urlstr, char **host, PRInt32 *port, char **path, int *sslOn)
{
    PRStatus status = PR_SUCCESS;
    char *brk;
    int len;
    static const char delimiters[] = ":/?#";
    char *url = isHttpReq(urlstr, sslOn);

    if (*sslOn) {
        *port = 443;
    } else {
        *port = 80;
    }
    if (url == NULL) {
        /* Error : */
        status = PR_FAILURE;
        goto bail;
    }
    len = PL_strlen(url);
    /* Currently we do not support Ipv6 addresses */
    brk = PL_strpbrk(url, delimiters);
    if (!brk) {
        *host = PL_strndup(url, len);
        toLowerCase(*host);
        goto bail;
    }
    switch (*brk) {
    case '/':
    case '?':
    case '#':
        /* Get the Host, the rest is Path */
        *host = PL_strndup(url, (brk - url));
        toLowerCase(*host);
        status = parseAtPath(brk, path);
        break;
    case ':':
        /* Get the Host and process port, path */
        *host = PL_strndup(url, (brk - url));
        toLowerCase(*host);
        status = parseAtPort(brk + 1, port, path);
        break;
    default:
        /* Error : HTTP_BAD_URL */
        break;
    }

bail:
    if (url) {
        PR_Free(url);
    }
    return status;
}

static PRStatus
parseAtPort(const char *url, PRInt32 *port, char **path)
{
    PRStatus status = PR_SUCCESS;
    static const char delimiters[] = "/?#";
    char *brk = PL_strpbrk(url, delimiters);
    if (!brk) /* everything is a Port */
    {
        *port = getPort(url);
        if (*port <= 0) {
            /* Error : HTTP_BAD_URL */
            return PR_FAILURE;
        } else {
            return status;
        }
    }

    switch (*brk) {
    case '/':
    case '?':
    case '#':
        /* Get the Port, the rest is Path */
        *port = getPort(url);
        if (*port <= 0) {
            /* Error : HTTP_BAD_URL */
            return PR_FAILURE;
        }
        status = parseAtPath(brk, path);
        break;
    default:
        /* Error : HTTP_BAD_URL */
        break;
    }
    return status;
}

static PRStatus
parseAtPath(const char *url, char **path)
{
    PRStatus status = PR_SUCCESS;
    char *dir = "%s%s";

    *path = (char *)PR_Calloc(1, strlen(dir) + strlen(url) + 2);
    if (!*path) {
        /* Error : HTTP_BAD_URL */
        status = PR_FAILURE;
        goto out;
    }

    /* Just write the path and check for a starting / */
    if ('/' != *url) {
        sprintf(*path, dir, "/", url);
    } else {
        strcpy(*path, url);
    }
out:
    return status;
}

static void
toLowerCase(char *str)
{
    if (str) {
        char *lstr = str;
        PRInt8 shift = 'a' - 'A';
        for (; (*lstr != '\0'); ++lstr) {
            if ((*(lstr) <= 'Z') && (*(lstr) >= 'A')) {
                *(lstr) = *(lstr) + shift;
            }
        }
    }
}

static PRInt32
getPort(const char *src)
{
    /* search for digits up to a slash or the string ends */
    const char *port = src;
    PRInt32 returnValue = -1;
    char c;

    /* skip leading white space */
    while (isAsciiSpace(*port))
        port++;

    while ((c = *port++) != '\0') {
        /* stop if slash or ? or # reached */
        if (c == '/' || c == '?' || c == '#')
            break;
        else if (!isAsciiDigit(c))
            return returnValue;
    }
    return (0 < PR_sscanf(src, "%d", &returnValue)) ? returnValue : -1;
}


static PRBool
isAsciiSpace(char aChar)
{
    if ((aChar == ' ') || (aChar == '\r') || (aChar == '\n') || (aChar == '\t')) {
        return PR_TRUE;
    }
    return PR_FALSE;
}

static PRBool
isAsciiDigit(char aChar)
{
    if ((aChar >= '0') && (aChar <= '9')) {
        return PR_TRUE;
    }
    return PR_FALSE;
}

static void
setTCPNoDelay(PRFileDesc *fd)
{
    PRStatus status = PR_SUCCESS;
    PRSocketOptionData opt;

    opt.option = PR_SockOpt_NoDelay;
    opt.value.no_delay = PR_FALSE;

    status = PR_GetSocketOption(fd, &opt);
    if (status == PR_FAILURE) {
        return;
    }

    opt.option = PR_SockOpt_NoDelay;
    opt.value.no_delay = PR_TRUE;
    status = PR_SetSocketOption(fd, &opt);
    if (status == PR_FAILURE) {
        return;
    }
    return;
}

static char *
isHttpReq(const char *url, int *sslOn)
{
    static const char http_protopol_header[] = "http://";
    static const char https_protopol_header[] = "https://";
    char *newstr = NULL;
    /* skip leading white space */
    while (isAsciiSpace(*url)) {
        url++;
    }

    if (strncmp(url, http_protopol_header, strlen(http_protopol_header)) == 0) {
        newstr = (char *)PR_Calloc(1, (strlen(url) - strlen(http_protopol_header) + 1));
        strcpy(newstr, url + 7);
        strcat(newstr, "\0");
        *sslOn = 0;
    } else if (strncmp(url, https_protopol_header, strlen(https_protopol_header)) == 0) {
        newstr = (char *)PR_Calloc(1, (strlen(url) - strlen(https_protopol_header) + 1));
        strcpy(newstr, url + 8);
        strcat(newstr, "\0");
        *sslOn = 1;
    }

    return newstr;
}

PRFileDesc *
setupSSLSocket(PRFileDesc *fd)
{
    SECStatus secStatus;
    PRFileDesc *sslSocket;
    PRSocketOptionData socketOption;
    char *certNickname = NULL;

    socketOption.option = PR_SockOpt_Nonblocking;
    socketOption.value.non_blocking = PR_FALSE;
    if (PR_SetSocketOption(fd, &socketOption) != 0) {
        slapi_log_err(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM,
                      "setupSSLSocket - Cannot set socket option NSS \n");
        return NULL;
    }

    sslSocket = SSL_ImportFD(NULL, fd);
    if (!sslSocket) {
        slapi_log_err(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM,
                      "setupSSLSocket - Cannot import to SSL Socket\n");
        goto sslbail;
    }

    slapi_log_err(SLAPI_LOG_ERR, HTTP_PLUGIN_SUBSYSTEM,
                  "setupSSLSocket - setupssl socket created\n");

    secStatus = SSL_OptionSet(sslSocket, SSL_SECURITY, 1);
    if (SECSuccess != secStatus) {
        slapi_log_err(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM,
                      "setupSSLSocket - Cannot set SSL_SECURITY option\n");
        goto sslbail;
    }

    secStatus = SSL_OptionSet(sslSocket, SSL_HANDSHAKE_AS_CLIENT, 1);
    if (SECSuccess != secStatus) {
        slapi_log_err(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM,
                      "setupSSLSocket - Cannot set SSL_HANDSHAKE_AS_CLIENT option\n");
        goto sslbail;
    }

    /* Set SSL callback routines. */

    secStatus = SSL_GetClientAuthDataHook(sslSocket,
                                          (SSLGetClientAuthData)getClientAuthData,
                                          (void *)certNickname);
    if (secStatus != SECSuccess) {
        slapi_log_err(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM,
                      "setupSSLSocket - SSL_GetClientAuthDataHook Failed\n");
        goto sslbail;
    }

    secStatus = SSL_AuthCertificateHook(sslSocket,
                                        (SSLAuthCertificate)authCertificate,
                                        (void *)CERT_GetDefaultCertDB());
    if (secStatus != SECSuccess) {
        slapi_log_err(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM,
                      "setupSSLSocket - SSL_AuthCertificateHook Failed\n");
        goto sslbail;
    }

    secStatus = SSL_BadCertHook(sslSocket,
                                (SSLBadCertHandler)badCertHandler, NULL);
    if (secStatus != SECSuccess) {
        slapi_log_err(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM,
                      "setupSSLSocket - SSL_BadCertHook Failed\n");
        goto sslbail;
    }

    secStatus = SSL_HandshakeCallback(sslSocket,
                                      (SSLHandshakeCallback)handshakeCallback, NULL);
    if (secStatus != SECSuccess) {
        slapi_log_err(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM,
                      "setupSSLSocket - SSL_HandshakeCallback Failed\n");
        goto sslbail;
    }

    return sslSocket;

sslbail:
    PR_Close(fd);
    return NULL;
}

SECStatus
authCertificate(void *arg, PRFileDesc *socket, PRBool checksig, PRBool isServer)
{

    SECCertUsage certUsage;
    CERTCertificate *cert;
    void *pinArg;
    char *hostName;
    SECStatus secStatus;

    if (!arg || !socket) {
        slapi_log_err(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM,
                      "authCertificate - Faulty socket in callback function\n");
        return SECFailure;
    }

    /* Define how the cert is being used based upon the isServer flag. */

    certUsage = isServer ? certUsageSSLClient : certUsageSSLServer;

    cert = SSL_PeerCertificate(socket);

    pinArg = SSL_RevealPinArg(socket);

    secStatus = CERT_VerifyCertNow((CERTCertDBHandle *)arg,
                                   cert,
                                   checksig,
                                   certUsage,
                                   pinArg);

    /* If this is a server, we're finished. */
    if (isServer || secStatus != SECSuccess) {
        return secStatus;
    }

    hostName = SSL_RevealURL(socket);

    if (hostName && hostName[0]) {
        secStatus = CERT_VerifyCertName(cert, hostName);
    } else {
        PR_SetError(SSL_ERROR_BAD_CERT_DOMAIN, 0);
        secStatus = SECFailure;
    }

    if (hostName)
        PR_Free(hostName);

    return secStatus;
}

SECStatus
badCertHandler(void *arg, PRFileDesc *socket __attribute__((unused)))
{

    SECStatus secStatus = SECFailure;
    PRErrorCode err;

    /* log invalid cert here */

    if (!arg) {
        return secStatus;
    }

    *(PRErrorCode *)arg = err = PORT_GetError();
    switch (err) {
    case SEC_ERROR_INVALID_AVA:
    case SEC_ERROR_INVALID_TIME:
    case SEC_ERROR_BAD_SIGNATURE:
    case SEC_ERROR_EXPIRED_CERTIFICATE:
    case SEC_ERROR_UNKNOWN_ISSUER:
    case SEC_ERROR_UNTRUSTED_CERT:
    case SEC_ERROR_CERT_VALID:
    case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
    case SEC_ERROR_CRL_EXPIRED:
    case SEC_ERROR_CRL_BAD_SIGNATURE:
    case SEC_ERROR_EXTENSION_VALUE_INVALID:
    case SEC_ERROR_CA_CERT_INVALID:
    case SEC_ERROR_CERT_USAGES_INVALID:
    case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION:
        secStatus = SECSuccess;
        break;
    default:
        secStatus = SECFailure;
        break;
    }

    slapi_log_err(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM,
                  "badCertHandler - Bad certificate: %d\n", err);

    return secStatus;
}

SECStatus
getClientAuthData(void *arg,
                  PRFileDesc *socket,
                  struct CERTDistNamesStr *caNames,
                  struct CERTCertificateStr **pRetCert,
                  struct SECKEYPrivateKeyStr **pRetKey)
{
    CERTCertificate *cert;
    SECKEYPrivateKey *privKey = NULL;
    char *chosenNickName = (char *)arg;
    void *proto_win = NULL;
    SECStatus secStatus = SECFailure;
    proto_win = SSL_RevealPinArg(socket);

    if (chosenNickName) {
        cert = PK11_FindCertFromNickname(chosenNickName, proto_win);
        if (cert) {
            privKey = PK11_FindKeyByAnyCert(cert, proto_win);
            if (privKey) {
                secStatus = SECSuccess;
            } else {
                CERT_DestroyCertificate(cert);
            }
        }
    } else { /* no nickname given, automatically find the right cert */
        CERTCertNicknames *names;
        int i;

        names = CERT_GetCertNicknames(CERT_GetDefaultCertDB(),
                                      SEC_CERT_NICKNAMES_USER, proto_win);

        if (names != NULL) {
            for (i = 0; i < names->numnicknames; i++) {

                cert = PK11_FindCertFromNickname(names->nicknames[i],
                                                 proto_win);
                if (!cert) {
                    continue;
                }

                /* Only check unexpired certs */
                if (CERT_CheckCertValidTimes(cert, PR_Now(), PR_FALSE) != secCertTimeValid) {
                    CERT_DestroyCertificate(cert);
                    continue;
                }

                secStatus = NSS_CmpCertChainWCANames(cert, caNames);
                if (secStatus == SECSuccess) {
                    privKey = PK11_FindKeyByAnyCert(cert, proto_win);
                    if (privKey) {
                        break;
                    }
                    secStatus = SECFailure;
                    break;
                }
                CERT_FreeNicknames(names);
            } /* for loop */
        }
    }

    if (secStatus == SECSuccess) {
        *pRetCert = cert;
        *pRetKey = privKey;
    }

    return secStatus;
}

SECStatus
handshakeCallback(PRFileDesc *socket __attribute__((unused)), void *arg __attribute__((unused)))
{
    slapi_log_err(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM,
                  "handshakeCallback - Handshake has completed, ready to send data securely.\n");
    return SECSuccess;
}


/**
 * PUBLIC FUNCTIONS IMPLEMENTATION
 */
int
http_impl_init(Slapi_ComponentId *plugin_id)
{
    int status = HTTP_IMPL_SUCCESS;
    slapi_log_err(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM,
                  "-> http_impl_init\n");
    httpConfig = NULL;

    httpConfig = (httpPluginConfig *)slapi_ch_calloc(1, sizeof(httpPluginConfig));

    status = readConfigLDAPurl(plugin_id, HTTP_PLUGIN_DN);
    if (status != 0) {
        slapi_log_err(SLAPI_LOG_ERR, HTTP_PLUGIN_SUBSYSTEM,
                      "http_impl_init - Unable to get HTTP config information \n");
        return HTTP_IMPL_FAILURE;
    }

    status = readConfigLDAPurl(plugin_id, CONFIG_DN);
    if (status != 0) {
        slapi_log_err(SLAPI_LOG_ERR, HTTP_PLUGIN_SUBSYSTEM,
                      "http_impl_init - Unable to get config information \n");
        return HTTP_IMPL_FAILURE;
    }

    slapi_log_err(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM,
                  "<- http_impl_init\n");

    return status;
}


int
http_impl_get_text(char *url, char **data, int *bytesRead)
{
    int status = HTTP_IMPL_SUCCESS;
    status = doRequestRetry(url, NULL, NULL, data, bytesRead, HTTP_REQ_TYPE_GET);
    return status;
}

int
http_impl_get_binary(char *url, char **data, int *bytesRead)
{
    int status = HTTP_IMPL_SUCCESS;
    status = doRequestRetry(url, NULL, NULL, data, bytesRead, HTTP_REQ_TYPE_GET);
    return status;
}

int
http_impl_get_redirected_uri(char *url, char **data, int *bytesRead)
{
    int status = HTTP_IMPL_SUCCESS;
    status = doRequestRetry(url, NULL, NULL, data, bytesRead, HTTP_REQ_TYPE_REDIRECT);
    return status;
}

int
http_impl_post(char *url, httpheader **httpheaderArray, char *body, char **data, int *bytesRead)
{
    int status = HTTP_IMPL_SUCCESS;
    status = doRequestRetry(url, httpheaderArray, body, data, bytesRead, HTTP_REQ_TYPE_POST);
    return status;
}

void
http_impl_shutdown(void)
{
    /**
     * Put cleanup code here
     */
}

static int
readConfigLDAPurl(Slapi_ComponentId *plugin_id, char *plugindn)
{

    int rc = LDAP_SUCCESS;
    Slapi_DN *sdn = NULL;
    int status = HTTP_IMPL_SUCCESS;
    Slapi_Entry *entry = NULL;

    sdn = slapi_sdn_new_dn_byref(plugindn);
    rc = slapi_search_internal_get_entry(sdn, NULL, &entry, plugin_id);
    slapi_sdn_free(&sdn);
    if (rc != LDAP_SUCCESS) {
        slapi_log_err(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM,
                      "readConfigLDAPurl - Could not find entry %s (error %d)\n", plugindn, rc);
        status = HTTP_IMPL_FAILURE;
        return status;
    }
    if (NULL == entry) {
        slapi_log_err(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM,
                      "readConfigLDAPurl - No entries found for <%s>\n", plugindn);

        status = HTTP_IMPL_FAILURE;
        return status;
    }

    if ((PL_strcasecmp(plugindn, HTTP_PLUGIN_DN) == 0))
        status = parseHTTPConfigEntry(entry);
    else
        status = parseConfigEntry(entry);

    slapi_entry_free(entry);
    return status;
}

/* Retrieves the plugin configuration info */

/* Retrieves security info as well as the path info required for the SSL
config dir */
static int
parseConfigEntry(Slapi_Entry *e)
{
    char *value = NULL;

    value = slapi_entry_attr_get_charptr(e, ATTR_DS_SECURITY);
    if (value) {
        httpConfig->DS_sslOn = value;
    }

    return HTTP_IMPL_SUCCESS;
}


static int
parseHTTPConfigEntry(Slapi_Entry *e)
{
    int value = 0;


    value = slapi_entry_attr_get_int(e, ATTR_RETRY_COUNT);
    if (value) {
        httpConfig->retryCount = value;
    }

    value = slapi_entry_attr_get_int(e, ATTR_CONNECTION_TIME_OUT);
    if (value) {
        httpConfig->connectionTimeOut = value;
    } else {
        slapi_log_err(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, "parseHTTPConfigEntry - HTTP Connection Time Out cannot be read. Setting to default value of 5000 ms \n");
        httpConfig->connectionTimeOut = 5000;
    }


    value = slapi_entry_attr_get_int(e, ATTR_READ_TIME_OUT);
    if (value) {
        httpConfig->readTimeOut = value;
    } else {
        slapi_log_err(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, "parseHTTPConfigEntry - HTTP Read Time Out cannot be read. Setting to default value of 5000 ms \n");
        httpConfig->readTimeOut = 5000;
    }

    httpConfig->nssInitialized = 0;

    return HTTP_IMPL_SUCCESS;
}

/**
 * Self Testing
 */
#ifdef BUILD_STANDALONE
int
main(int argc, char **argv)
{
    PRStatus status = PR_SUCCESS;
    char *buf;
    int bytes;
    char *host;
    PRInt32 port;
    char *path;
    if (argc < 2) {
        printf("URL missing\n");
        return -1;
    }
    PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
    doRequest(argv[1], &buf, &bytes, 2);
    printf("%s\n", buf);
    return -1;
}
#endif
