(file) Return to httpclient.c CVS log (file) (dir) Up to [OMI] / omi / http

File: [OMI] / omi / http / httpclient.c (download)
Revision: 1.4, Fri Sep 25 19:24:20 2015 UTC (8 years, 7 months ago) by krisbash
Branch: MAIN
CVS Tags: OMI_1_0_8_2, HEAD
Changes since 1.3: +46 -20 lines
OMI 1.0.8-2 commit

/*
**==============================================================================
**
** Open Management Infrastructure (OMI)
**
** Copyright (c) Microsoft Corporation
**
** Licensed under the Apache License, Version 2.0 (the "License"); you may not
** use this file except in compliance with the License. You may obtain a copy
** of the License at
**
**     http://www.apache.org/licenses/LICENSE-2.0
**
** THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
** KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
** WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
** MERCHANTABLITY OR NON-INFRINGEMENT.
**
** See the Apache 2 License for the specific language governing permissions
** and limitations under the License.
**
**==============================================================================
*/

#include <assert.h>
#include <ctype.h>
#include "httpcommon.h"
#include "httpclient.h"
#include <sock/addr.h>
#include <sock/sock.h>
#include <sock/selector.h>
#include <pal/sleep.h>
#include <base/buf.h>
#include <base/log.h>
#include <base/result.h>
#include <pal/intsafe.h>
#include <pal/strings.h>
#include <pal/format.h>
#include <pal/sleep.h>
#include <base/paths.h>

// #define ENABLE_TRACING 1
#ifdef ENABLE_TRACING
# define TRACING_LEVEL 4
# include <deprecated/logging/logging.h>
#else
# define LOGE2(a)
# define LOGW2(a)
# define LOGD2(a)
# define LOGX2(a)
#endif

#ifdef CONFIG_POSIX
#include <openssl/ssl.h>
#include <openssl/err.h>
#else
/* ssl not supported in this configuration; just make compiler happy */
typedef void SSL;
typedef void SSL_CTX;
#define SSL_CTX_free(c)
#define SSL_new(c) 0
#define SSL_free(c)
#define SSL_set_connect_state(c)
#define SSL_set_fd(c,a) (a==a)
#define SSL_read(c,a,b) 0
#define SSL_write(c,a,b) 0
#define SSL_get_error(c,e) e
#define SSL_ERROR_WANT_WRITE 0
#define SSL_ERROR_WANT_READ 1
#define SSL_ERROR_SYSCALL 2

#ifdef EWOULDBLOCK
# undef EWOULDBLOCK
#endif

#define EWOULDBLOCK 0

#ifdef EINPROGRESS
# undef EINPROGRESS
#endif

#define EINPROGRESS 0

#define ERR_get_error() 0
#define ERR_error_string_n(c,a,b) a[0]=0
#define SSL_accept(c) 0
#define SSL_connect(c) 0

#endif

/*
**==============================================================================
**
** Local definitions:
**
**==============================================================================
*/

static const MI_Uint32 _MAGIC = 0x5FC7B966;
static const MI_Uint32 MAX_HEADER_SIZE = 2 * 1024;
static const MI_Uint32 INITIAL_BUFFER_SIZE = 2 * 1024;
static const MI_Uint32 DEFAULT_HTTP_TIMEOUT_USEC = 60 * 1000000;

typedef enum _Http_RecvState
{
    RECV_STATE_HEADER,
    RECV_STATE_CONTENT,
    RECV_STATE_CHUNKHEADER,
    RECV_STATE_CHUNKDATA
}
Http_RecvState;

typedef struct _HttpClient_SR_SocketData
{
    /* based member*/
    Handler     base;

    /* timeout */
    MI_Uint64   timeoutUsec;

    /* ssl part */
    SSL*  ssl;
    MI_Boolean  reverseOperations;  /*reverse read/write Events/Handlers*/
    MI_Boolean  connectDone;

    /* receiving data */
    __field_ecount(recvBufferSize) char* recvBuffer;
    size_t      recvBufferSize;
    size_t      receivedSize;
    Http_RecvState      recvingState;
    HttpClientHeaderField recvHeaderFields[64];
    HttpClientResponseHeader   recvHeaders;
    MI_Sint64   contentLength;
    MI_Sint64   contentBegin;
    MI_Sint64   contentEnd;
    MI_Sint64   contentTotalLength;
    Page*   recvPage;

    /* flag for a response from a HEAD request */
    MI_Boolean  headVerb;

    /* sending part */
    Page*   sendPage;
    Page*   sendHeader;
    size_t      sentSize;
    Http_RecvState  sendingState;

    /* general operation status */
    MI_Result status;

}
HttpClient_SR_SocketData;

struct _HttpClient
{
    MI_Uint32       magic;
    Selector        internalSelector;
    Selector*       selector;
    HttpClientCallbackOnStatus     callbackOnStatus;
    HttpClientCallbackOnResponse   callbackOnResponse;
    void*                               callbackData;
    SSL_CTX*    sslContext;

    HttpClient_SR_SocketData* connector;

    MI_Boolean  internalSelectorUsed;
};


/* helper functions result */
typedef enum _Http_CallbackResult
{
    PRT_RETURN_FALSE,
    PRT_RETURN_TRUE,
    PRT_CONTINUE
}
Http_CallbackResult;


MI_INLINE MI_Uint8 _ToLower(MI_Uint8 x)
{
    return (MI_Uint8)tolower(x);
}

#define _HashCode(first,last,len) ( (((MI_Uint8)first) << 16) | (((MI_Uint8)last) << 8)  | (((MI_Uint16)len)) )

_Return_type_success_(return == MI_TRUE)
static MI_Boolean _getNameValuePair(
    _Inout_ CharPtr* line,
    _Out_ CharPtr* value,
    _Out_ int*  nameHashCode)
{
    int len = 0;
    char* p;
    /* find name end /hash-code */

    if ((*line)[0] == '\0')
    {
        return MI_FALSE;
    }

    *nameHashCode =  _ToLower((MI_Uint8)(*line)[0])<<16;

    for (len = 1; (*line)[len] != '\0' && (*line)[len] != ':' && (*line)[len] != '\r'; len++)
        ;

    if ((*line)[len] != ':')
        return MI_FALSE;

    *nameHashCode |=  (len) | _ToLower((MI_Uint8)(*line)[len-1])<<8;
    (*line)[len] = 0;
    p = *line + len + 1;

    /* skip spaces in value */
    while (p[0] == ' ' || p[0] == '\t')
        p++;

    *value = p;

    /* skip to end of line */
    for ( ; p[0]; )
    {
        if (p[0] != '\0' && p[1] != '\0' && p[2] != '\0' &&
            p[0] == '\r' && p[1] == '\n' &&
            p[2] != ' '  && p[2] != '\t')
        {
            p[0] = '\0';
            (*line) = p + 2;
            break;
        }
        p++;
    }

    /* remove trailing spaces */
    p--;
#ifdef _PREFAST_
#pragma prefast (push)
#pragma prefast (disable: 26001)
#endif
    /* disabling IPv6 OACR warnings - D3M bug 56 */
    while (p[0] == ' ' || p[0] == '\t')
        p--;
#ifdef _PREFAST_
#pragma prefast (pop)
#endif

    p[1] = '\0';

    return MI_TRUE;
}

static MI_Boolean _getHeaderField(
    HttpClient_SR_SocketData* handler,
    _Inout_ CharPtr* line)
{
    /* expecting  Request-Line = Method SP Request-URI SP HTTP-Version CRLF
        Read more: http://www.faqs.org/rfcs/rfc2616.html#ixzz0jKdjJdZv
    */
    char* name = *line;
    char* value = NULL;
    int nameHashCode;

    if (!_getNameValuePair(line, &value, &nameHashCode))
        return MI_FALSE;

    if (nameHashCode == _HashCode('c', 'h', 14) && /*Content-Length*/
        Strcasecmp(name,"Content-Length") == 0)
    {
        handler->contentLength = Strtoull(value, NULL, 10);
        /*if (handler->contentLength > HTTP_MAX_CONTENT)
        handler->contentBegin = -1;
        handler->contentEnd = -1;
        handler->contentTotalLength = -1;
        return MI_FALSE;*/
    }
    else if (nameHashCode == _HashCode('t', 'g', 17) && /*Transfer-Encoding*/
        Strcasecmp(name,"Transfer-Encoding") == 0)
    {
        handler->contentLength = -1;
        handler->contentBegin = -1;
        handler->contentEnd = -1;
        handler->contentTotalLength = -1;
    }
    else if (nameHashCode == _HashCode('c','e',13) && /*Content-Range*/
        Strcasecmp(name, "Content-Range") == 0)
    {
        char* delimptr;
        char* endptr;
        handler->contentEnd = -1;
        handler->contentBegin = (MI_Sint64)Strtoull(value, NULL, 10);
        delimptr = strchr(value, '-');
        endptr = strchr(value, '\n');
        if (delimptr != NULL && (endptr == NULL || endptr > delimptr))
            handler->contentEnd = Strtoull(++delimptr, NULL, 10);
        delimptr = strchr(value, '/');
        if (delimptr != NULL && (endptr == NULL || endptr > delimptr))
            handler->contentTotalLength = Strtoull(++delimptr, NULL, 10);
    }
    if (handler->recvHeaders.sizeHeaders < MI_COUNT(handler->recvHeaderFields))
    {
        handler->recvHeaderFields[handler->recvHeaders.sizeHeaders].name = name;
        handler->recvHeaderFields[handler->recvHeaders.sizeHeaders].value = value;
        handler->recvHeaders.sizeHeaders++;
    }
    else
    {
        trace_TooManyHttpHeaders(scs(name), scs(value));
    }

    return MI_TRUE;
}

static MI_Boolean _getChunkSize(
    const char * line,
    MI_Uint32* chunkSize)
{
    *chunkSize = 0;

    while (*line)
    {
        char c = *line;

        if (c >= '0' && c <= '9')
            *chunkSize = *chunkSize * 16 + (c - '0');
        else if (c >= 'a' && c <= 'f')
            *chunkSize = *chunkSize * 16 + (c - 'a' + 10);
        else if (c >= 'A' && c <= 'F')
            *chunkSize = *chunkSize * 16 + (c - 'A' + 10);
        else
            break;

        line++;
    }

    return MI_TRUE;
}


static MI_Boolean _getRequestLine(
    HttpClient_SR_SocketData* handler,
    _Inout_ CharPtr* line)
{
    size_t index;
    /* expecting  Request-Line = Method SP Request-URI SP HTTP-Version CRLF
        Read more: http://www.faqs.org/rfcs/rfc2616.html#ixzz0jKdjJdZv
    */

    /* initialize header */
    handler->recvHeaders.sizeHeaders = 0;
    handler->recvHeaders.headers = handler->recvHeaderFields;

    /* find http code */
    {
        /* skip http version, that is in format
                HTTP-Version   = "HTTP" "/" 1*DIGIT "." 1*DIGIT
                http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.1

                Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
        */
        const char* s = *line; /*+ HTTP/1.1<sp>*/
        size_t skips = 9;
        for (; skips > 0; s++)
        {
            skips--;
            if (!*s)
            {
                return MI_FALSE;
            }
        }

        handler->recvHeaders.httpError = Strtoul(s, NULL, 10);
    }

    /* skip to end of line */
    for (index = 1; (*line)[index] && index < handler->receivedSize; index++)
    {
        if ((*line)[index-1] == '\r' && (*line)[index] == '\n')
        {
            (*line) = (*line) + index + 1;
            return MI_TRUE;
        }
    }

    return MI_FALSE;
}

static MI_Result _Sock_Read(
    HttpClient_SR_SocketData* handler,
    void* buf,
    size_t buf_size,
    size_t* sizeRead)
{
    int res;
    int sslError;

    if (handler->ssl == NULL)
    {
        MI_Result res = Sock_Read(handler->base.sock, buf, buf_size, sizeRead);

        LOGD2((ZT("_Sock_Read - After regular read. socket: %d, result: %d (%s), bytes read: %u / %u"), handler->base.sock, (int)res, mistrerror(res), (unsigned int)*sizeRead, (unsigned int)buf_size));
        return res;
    }

    handler->base.mask &= ~SELECTOR_WRITE;
    handler->base.mask |= SELECTOR_READ;
    handler->reverseOperations = MI_FALSE;

    *sizeRead = 0;

    res = SSL_read(handler->ssl, buf, buf_size);
    LOGD2((ZT("_Sock_Read - SSL_Read returned: %d (< 0 for error) / %u bytes read, errno: %d (%s)"), res, (unsigned int)buf_size, errno, strerror(errno)));
    if (res == 0)
    {
        LOGW2((ZT("_Sock_Read - SSL socket connection closed. socket: %d"), handler->base.sock));
        return MI_RESULT_OK;    /* connection closed */
    }

    if (res > 0)
    {
        LOGD2((ZT("_Sock_read - Bytes read: %d"), res));
        *sizeRead = res;
        return MI_RESULT_OK;    /* ok */
    }

    sslError = SSL_get_error(handler->ssl, res);
    switch (sslError)
    {
    case SSL_ERROR_WANT_WRITE:
        handler->reverseOperations = MI_TRUE;   /* wait until write is allowed */
        handler->base.mask &= ~SELECTOR_READ;
        handler->base.mask |= SELECTOR_WRITE;
        LOGD2((ZT("_Sock_Read - SSL_read/accept returned WANT_WRITE")));
        return MI_RESULT_WOULD_BLOCK;

    case SSL_ERROR_WANT_READ:
        LOGD2((ZT("Sock_Read - SSL_read/accept returned WANT_READ")));
        return MI_RESULT_WOULD_BLOCK;

    case SSL_ERROR_SYSCALL:
        if (EAGAIN == errno ||
            EWOULDBLOCK == errno ||
            EINPROGRESS == errno)
            return MI_RESULT_WOULD_BLOCK;

        LOGE2((ZT("Sock_Read - SSL_read returned OS error %d (%s)"), errno, strerror(errno)));
        trace_SSLRead_UnexpectedSysError(errno);
        break;

    default:
        /* print Open SSL error stack */
        {
            unsigned long err;
            while ((err = ERR_get_error()) != 0)
            {
                char err_txt[200];

                ERR_error_string_n(err, err_txt, sizeof (err_txt));
                LOGE2((ZT("_Sock_Read - SSL_read returned OpenSSL error: %lu (%s)"), err, err_txt));
            }
        }
        break;
    }
    return MI_RESULT_FAILED;
}

static MI_Result _Sock_Write(
    HttpClient_SR_SocketData* handler,
    void* buf,
    size_t buf_size,
    size_t* sizeWritten)
{
    int res;
    int sslError;

    if (!handler->ssl)
    {
        MI_Result res = Sock_Write(handler->base.sock, buf, buf_size, sizeWritten);
        LOGD2((ZT("_Sock_Write - Non-SSS write. Sock_Write returned %d (%s). %u / %u bytes sent"), res, mistrerror(res), (unsigned int)*sizeWritten, (unsigned int)buf_size));
        return res;
    }

    /* Do not clear READ flag, since 'close' notification
       delivered as READ event */
    handler->base.mask &= ~SELECTOR_READ;
    handler->base.mask |= SELECTOR_WRITE;
    handler->reverseOperations = MI_FALSE;

    *sizeWritten = 0;

    if (handler->connectDone)
    {
        res = SSL_write(handler->ssl, buf, buf_size);
        LOGD2((ZT("_Sock_Write - SSL_write using socket %d returned %d (< 0 for error) / %u bytes written, errno: %d (%s)"), handler->base.sock, res, (unsigned int)buf_size, errno, strerror(errno)));
    }
    else
    {
        res = SSL_connect(handler->ssl);
        LOGD2((ZT("_Sock_Write - SSL connect using socket %d returned result: %d, errno: %d (%s)"), handler->base.sock, res, errno, strerror(errno)));
        if (res > 0)
        {
            /* we are done with accept */
            handler->connectDone = MI_TRUE;
            return _Sock_Write(handler,buf,buf_size,sizeWritten);
        }
        /* perform regular error checking */
    }


    if (res == 0)
    {
        LOGW2((ZT("_Sock_Write - SSL socket connection closed")));
        return MI_RESULT_OK;    /* connection closed */
    }

    if (res > 0)
    {
        *sizeWritten = res;
        LOGD2((ZT("_Sock_Write - SSL socket successful write of %d / %u bytes"), res, (unsigned int)buf_size));
        return MI_RESULT_OK;    /* ok */
    }

    sslError = SSL_get_error(handler->ssl, res);
    switch (sslError)
    {
    case SSL_ERROR_WANT_WRITE:
        LOGD2((ZT("_Sock_Write - SSL_write/connect returned WANT_WRITE")));
        return MI_RESULT_WOULD_BLOCK;

    case SSL_ERROR_WANT_READ:
        LOGD2((ZT("_Sock_Write - SSL_write/connect returned WANT_READ")));
        handler->reverseOperations = MI_TRUE;   /* wait until write is allowed */
        handler->base.mask |= SELECTOR_READ;
        handler->base.mask &= ~SELECTOR_WRITE;
        return MI_RESULT_WOULD_BLOCK;

    case SSL_ERROR_SYSCALL:
        if (EAGAIN == errno ||
            EWOULDBLOCK == errno ||
            EINPROGRESS == errno)
        {
            LOGD2((ZT("_Sock_Write - Returning WOULD_BLOCK. errno: %d (%s)"), errno, strerror(errno)));
            return MI_RESULT_WOULD_BLOCK;
        }

        LOGE2((ZT("_Sock_Write - SSL_write/connect returned unexpected OS error %d (%s)"), errno, strerror(errno)));
        trace_SSLWrite_UnexpectedSysError(errno);
        break;

    case SSL_ERROR_SSL:
        LOGE2((ZT("_Sock_Write - SSL_write/connect returned OpenSSL error %d (%s)"), sslError, ERR_error_string(sslError, NULL)));
        break;

    default:
        LOGD2((ZT("_Sock_Write - SSL_write/connect returned uncategorized OpenSSL error: %d"), res));
        break;
    }

    return MI_RESULT_FAILED;
}

static Http_CallbackResult _ReadHeader(
    HttpClient_SR_SocketData* handler)
{
    char* buf;
    char* currentLine;
    char* data;
    size_t contentSize;
    size_t buf_size, received, index;
    MI_Result r;
    MI_Boolean fullHeaderReceived = MI_FALSE;

    /* are we done with header? */
    if (handler->recvingState != RECV_STATE_HEADER)
        return PRT_CONTINUE;

    buf = handler->recvBuffer + handler->receivedSize;
    buf_size = handler->recvBufferSize - handler->receivedSize;
    received = 0;

    r = _Sock_Read(handler, buf, buf_size, &received);
    LOGD2((ZT("_ReadHeader - Begin. _Sock_read result: %d (%s), socket: %d, %u / %u bytes read, reverse: %d"), (int)r, mistrerror(r), (int)handler->base.sock, (unsigned int)received, (unsigned int)buf_size, (int)handler->reverseOperations));

    if (r == MI_RESULT_OK && 0 == received)
    {
        LOGW2((ZT("_ReadHeader - 0 bytes received without error. Socket closed?")));
        return PRT_RETURN_FALSE; /* connection closed */
    }

    if (r != MI_RESULT_OK && r != MI_RESULT_WOULD_BLOCK)
    {
        LOGE2((ZT("_ReadHeader - Error %d (%s)"), r, mistrerror(r)));
        return PRT_RETURN_FALSE;
    }

    if (received == 0)
    {
        LOGD2((ZT("_ReadHeader - 0 bytes received. Waiting...")));
        return PRT_RETURN_TRUE;
    }

    handler->receivedSize += received;

    /* check header */
    LOGD2((ZT("_ReadHeader - Received buffer: %s"), buf));

    /* did we get full header? */
    buf = handler->recvBuffer;
    LOGD2((ZT("_ReadHeader - Checking for full header...")));
    for ( index = 3; index < handler->receivedSize; index++ )
    {
        _Analysis_assume_(handler->recvBufferSize > 3);
        if (buf[index-3] == '\r' && buf[index-1] == '\r' &&
            buf[index-2] == '\n' && buf[index] == '\n' )
        {
            fullHeaderReceived = MI_TRUE;
            LOGD2((ZT("_ReadHeader - Full header has been received")));
            break;
        }
    }

    if (!fullHeaderReceived)
    {
        if (handler->receivedSize <  handler->recvBufferSize)
        {
            LOGD2((ZT("_ReadHeader - Full header not received. Waiting...")));
            return PRT_RETURN_TRUE; /* continue reading */
        }

        if (handler->recvBufferSize < MAX_HEADER_SIZE)
        {
            LOGD2((ZT("_ReadHeader - Reallocating buffer...")));
            buf = PAL_Realloc(handler->recvBuffer, handler->recvBufferSize * 2);

            if (!buf)
            {
                LOGE2((ZT("_ReadHeader - Cannot allocate memory for larger header")));
                return PRT_RETURN_FALSE;
            }

            handler->recvBufferSize *= 2;
            handler->recvBuffer = buf;
            LOGD2((ZT("_ReadHeader - Going recursive...")));
            return _ReadHeader(handler);
        }
        else
        {
            /* Http header is too big - drop connection */
            trace_HttpHeaderIsTooBig();
            LOGE2((ZT("_ReadHeader - HTTP header is too big. Dropping connection")));
            return PRT_RETURN_FALSE;
        }
    }

    /* consume data */
    currentLine = buf;
    data = buf + index + 1; /* pointer to data in case we got some */

    if (!_getRequestLine(handler, &currentLine))
    {
        LOGE2((ZT("_ReadHeader - Cannot find request line in HTTP header")));
        return PRT_RETURN_FALSE;
    }

    while ((data - currentLine) > 3)
    {
        if (!_getHeaderField(handler, &currentLine))
        {
            LOGE2((ZT("_ReadHeader - Cannot find HTTP header field")));
            return PRT_RETURN_FALSE;
        }
    }

    /* Check if we have to deal with chunked-encoded data */
    if (handler->contentLength < 0)
    {
        handler->receivedSize -= index + 1;

        /* Invoke user's callback with header information */
        {
            HttpClient* self = (HttpClient*)handler->base.data;

            if (!(*self->callbackOnResponse)(self, self->callbackData, &handler->recvHeaders,
                handler->contentLength, handler->contentLength == 0, 0))
            {
                LOGD2((ZT("_ReadHeader - On response callback for chunked data header failed")));
                return PRT_RETURN_FALSE;
            }
        }

        /* remove consumed header part */
        memmove(handler->recvBuffer, data, handler->receivedSize);

        handler->recvingState = RECV_STATE_CHUNKHEADER;
        return PRT_CONTINUE;
    }

    contentSize = (size_t)handler->contentLength;
    if (handler->headVerb)
    {
        LOGD2((ZT("_ReadHeader - HEAD response received. Download will contain %u bytes"), (unsigned int)contentSize));
        contentSize = 0;
    }

    size_t allocSize = 0;
    if (SizeTAdd(sizeof(Page), contentSize, &allocSize) == S_OK &&
        SizeTAdd(allocSize, 1, &allocSize) == S_OK)
    {
        /* Allocate zero-terminated buffer */
        handler->recvPage = (Page*)PAL_Malloc(allocSize);
    }
    else
    {
        // Overflow
        return PRT_RETURN_FALSE;
    }

    if (handler->recvPage == NULL)
    {
        LOGD2((ZT("_ReadHeader - Cannot allocate memory for received page")));
        return PRT_RETURN_FALSE;
    }
    ((char*)(handler->recvPage + 1))[contentSize] = '\0';

    handler->recvPage->u.s.size = (unsigned int)contentSize;
    handler->recvPage->u.s.next = 0;
    handler->receivedSize -= index + 1;

    /* Verify that we have not more than 'content-length' bytes in buffer left
        If we have more, assuming http client is invalid and drop connection */
    if (handler->receivedSize > contentSize)
    {
        trace_HttpPayloadIsBiggerThanContentLength();
        LOGE2((ZT("_ReadHeader - HTTP payload is bigger than content-length (%u > %u bytes)"), (unsigned int)handler->receivedSize, (unsigned int)contentSize));
        return PRT_RETURN_FALSE;
    }

    if (handler->receivedSize != 0)
        memcpy(handler->recvPage + 1, data, handler->receivedSize);
    handler->recvingState = RECV_STATE_CONTENT;

    /* Invoke user's callback with header information */
    {
        HttpClient* self = (HttpClient*)handler->base.data;

        if (!(*self->callbackOnResponse)(self, self->callbackData, &handler->recvHeaders,
            handler->contentLength, handler->contentLength == 0, 0))
        {
            LOGE2((ZT("_ReadHeader - On response callback for header failed")));
            return PRT_RETURN_FALSE;
        }
    }

    LOGD2((ZT("_ReadHeader - OK exit")));
    return PRT_CONTINUE;
}

static Http_CallbackResult _ReadData(
    HttpClient_SR_SocketData* handler)
{
    /* HttpClient* self = (HttpClient*)handler->base.data; */
    char* buf;
    size_t buf_size, received;
    MI_Result r;

    /* are we in the right state? */
    if (handler->recvingState != RECV_STATE_CONTENT)
        return PRT_RETURN_FALSE;

    LOGD2((ZT("_ReadData - Begin. Head? %d"), handler->headVerb));
    if (!handler->headVerb)
    {
        buf = (char*)(handler->recvPage + 1) + handler->receivedSize;
        buf_size = (size_t)(handler->contentLength - handler->receivedSize);
        received = 0;

        if (buf_size != 0)
        {
            r = _Sock_Read(handler, buf, buf_size, &received);
            LOGD2((ZT("_ReadData - _Sock_Read result: %d (%s), socket: %d, recv: %u"), (int)r, mistrerror(r), (int)handler->base.sock, (unsigned int)received));

            if (r == MI_RESULT_OK && 0 == received)
                return PRT_RETURN_FALSE; /* connection closed */

            if (r != MI_RESULT_OK && r != MI_RESULT_WOULD_BLOCK)
                return PRT_RETURN_FALSE;

            handler->receivedSize += received;

            LOGD2((ZT("_RequestCallback - Called _ReadData. %d / %d bytes read"), (int)handler->receivedSize, (int)handler->contentLength));

            if (handler->contentLength > 0 && handler->receivedSize < (size_t)handler->contentLength)
            {                           /* assume 500 bytes per millisecond transmission */
                                        /* wait to avoid spinning on _Sock_Read */
                unsigned int bytesLeft = (unsigned int)handler->contentLength - (unsigned int)handler->receivedSize;
                unsigned long msec = (unsigned long)(bytesLeft / 500 + 1);

                Sleep_Milliseconds(msec);
            }
        }

        /* did we get all data? */
        LOGD2((ZT("_ReadData - Received size: %d / %d"), (int)handler->receivedSize, (int)handler->contentLength));
        if (handler->receivedSize != (size_t)handler->contentLength)
            return PRT_RETURN_TRUE;
    }

    /* Invoke user's callback with header information */
    {
        HttpClient* self = (HttpClient*)handler->base.data;
        MI_Boolean lastChunk = MI_TRUE;

        if (handler->contentEnd >= 0 &&
            handler->contentEnd + 1 < handler->contentTotalLength)
        {
            lastChunk = MI_FALSE;
        }

        if (!(*self->callbackOnResponse)(self, self->callbackData, 0,
            handler->contentLength, lastChunk, &handler->recvPage))
            return PRT_RETURN_FALSE;

        /* status callback */
        handler->status = MI_RESULT_OK;
        (*self->callbackOnStatus)(
            self,
            self->callbackData,
            MI_RESULT_OK);
    }

    if (handler->recvPage != NULL)
    {
        LOGD2((ZT("_ReadData - Freeing recvPage. socket: %d"), (int)handler->base.sock));
        PAL_Free(handler->recvPage);
    }

    handler->recvPage = NULL;
    handler->receivedSize = 0;
    memset(&handler->recvHeaders, 0, sizeof(handler->recvHeaders));
    handler->recvingState = RECV_STATE_HEADER;
    LOGD2((ZT("_ReadData - OK exit")));
    return PRT_CONTINUE;
}

static Http_CallbackResult _ReadChunkHeader(
    HttpClient_SR_SocketData* handler)
{
    char* buf;
    char* currentLine;
    char* data;
    size_t buf_size, received, index;
    MI_Result r;
    MI_Boolean fullHeaderReceived = MI_FALSE;
    MI_Uint32   chunkSize = 0;
    MI_Boolean connectionClosed = MI_FALSE;

    /* are we done with header? */
    if (handler->recvingState != RECV_STATE_CHUNKHEADER)
        return PRT_CONTINUE;

    buf = handler->recvBuffer + handler->receivedSize;
    buf_size = handler->recvBufferSize - handler->receivedSize;
    received = 0;

    r = _Sock_Read(handler, buf, buf_size, &received);

    if (r == MI_RESULT_OK && 0 == received)
    {
        if (!handler->recvBufferSize)
            return PRT_RETURN_FALSE; /* connection closed */

        connectionClosed = MI_TRUE;
    }

    if (r != MI_RESULT_OK && r != MI_RESULT_WOULD_BLOCK)
        return PRT_RETURN_FALSE;

    if (!received && !handler->recvBufferSize)
        return PRT_RETURN_TRUE;

    handler->receivedSize += received;

    /* did we get full header? */
    buf = handler->recvBuffer;

    _Analysis_assume_(handler->recvBufferSize > 2);
    for (index = 1; index < handler->receivedSize && buf[index]; index++)
    {
        if (buf[index-1] == '\r' && buf[index] == '\n' )
        {
            fullHeaderReceived = MI_TRUE;
            break;
        }
    }

    if (!fullHeaderReceived)
    {
        if (connectionClosed)
            return PRT_RETURN_FALSE; /* connection closed */

        if (handler->receivedSize <  handler->recvBufferSize)
            return PRT_RETURN_TRUE; /* continue reading */

        if (handler->recvBufferSize < MAX_HEADER_SIZE)
        {
            buf = PAL_Realloc(handler->recvBuffer, handler->recvBufferSize * 2);

            if (!buf)
                return PRT_RETURN_FALSE;

            handler->recvBufferSize *= 2;
            handler->recvBuffer = buf;
            return _ReadChunkHeader(handler);
        }
        else
        {
            /* http chunk header is too big - drop connection */
            trace_HttpChunkHeaderIsTooBig();
            return PRT_RETURN_FALSE;
        }
    }

    /* consume data */
    currentLine = buf;
    data = buf + index + 1; /* pointer to data in case we got some */

    if (!_getChunkSize(currentLine, &chunkSize))
        return PRT_RETURN_FALSE;

    if (0 == chunkSize)
    {
        /* last chunk received */

        /* Invoke user's callback with header information */
        {
            HttpClient* self = (HttpClient*)handler->base.data;

            if (!(*self->callbackOnResponse)( self, self->callbackData, 0,
                handler->contentLength, MI_TRUE, 0))
                return PRT_RETURN_FALSE;

            /* status callback */
            handler->status = MI_RESULT_OK;
            (*self->callbackOnStatus)(
                self,
                self->callbackData,
                MI_RESULT_OK);
        }

        /* clean up state */
        handler->recvPage = 0;
        handler->receivedSize = 0;
        memset(&handler->recvHeaders, 0, sizeof(handler->recvHeaders));
        handler->recvingState = RECV_STATE_HEADER;

        if (connectionClosed)
            return PRT_RETURN_FALSE; /* connection closed */

        return PRT_CONTINUE;
    }

    size_t allocSize = 0;
    if (SizeTAdd(sizeof(Page), (size_t)chunkSize, &allocSize) == S_OK &&
        SizeTAdd(allocSize, 2 /*CR-LF*/ + 1 /* \0 */, &allocSize) == S_OK)
    {
        /* Allocate zero-terminated buffer */
        handler->recvPage = (Page*)PAL_Malloc(allocSize);
    }
    else
    {
        // Overflow
        return PRT_RETURN_FALSE;
    }

    if (!handler->recvPage)
        return PRT_RETURN_FALSE;

    ((char*)(handler->recvPage + 1))[chunkSize+2] = 0;

    handler->recvPage->u.s.size = (unsigned int)chunkSize;
    handler->recvPage->u.s.next = 0;

    /* subtract header size */
    handler->receivedSize -= index + 1;

    /* in case of small chunks we may receive more than one chunk already */
    if (handler->receivedSize > (size_t)(chunkSize+2))
    {
        /* copy page size to page */
        memcpy(handler->recvPage + 1, data, chunkSize+2);

        /* notify user */
        {
            HttpClient* self = (HttpClient*)handler->base.data;

            if (!(*self->callbackOnResponse)( self, self->callbackData, 0,
                handler->contentLength, MI_FALSE, &handler->recvPage))
                return PRT_RETURN_FALSE;

            if (handler->recvPage)
                PAL_Free(handler->recvPage);

            handler->recvPage = 0;
        }

        /* remove consumed part */
        memmove(handler->recvBuffer, data + chunkSize+2, handler->receivedSize - (chunkSize+2));
        handler->receivedSize -= (chunkSize+2);

        /* consume next chunk */
        return _ReadChunkHeader(handler);
    }

    memcpy(handler->recvPage + 1, data, handler->receivedSize);
    handler->recvingState = RECV_STATE_CHUNKDATA;

    if (connectionClosed)
        return PRT_RETURN_FALSE; /* connection closed */

    return PRT_CONTINUE;
}
static Http_CallbackResult _ReadChunkData(
    HttpClient_SR_SocketData* handler)
{
    //HttpClient* self = (HttpClient*)handler->base.data;
    char* buf;
    size_t buf_size, received;
    MI_Result r;

    /* are we in the right state? */
    if (handler->recvingState != RECV_STATE_CHUNKDATA)
        return PRT_RETURN_FALSE;

    buf = ((char*)(handler->recvPage + 1)) + handler->receivedSize;
    buf_size = (size_t)(handler->recvPage->u.s.size + 2 /* CR-LF */ - handler->receivedSize);
    received = 0;

    if (buf_size)
    {
        r = _Sock_Read(handler, buf, buf_size, &received);

        if (r == MI_RESULT_OK && 0 == received)
            return PRT_RETURN_FALSE; /* connection closed */

        if (r != MI_RESULT_OK && r != MI_RESULT_WOULD_BLOCK)
            return PRT_RETURN_FALSE;

        handler->receivedSize += received;
    }

    if (handler->receivedSize != (size_t)(handler->recvPage->u.s.size + 2 /* CR-LF */))
        return PRT_RETURN_TRUE;

    /* Invoke user's callback with header information */
    {
        HttpClient* self = (HttpClient*)handler->base.data;

        if (!(*self->callbackOnResponse)( self, self->callbackData, 0,
            handler->contentLength, MI_FALSE, &handler->recvPage))
            return PRT_RETURN_FALSE;

    }

    if (handler->recvPage)
        PAL_Free(handler->recvPage);

    handler->recvPage = 0;
    handler->receivedSize = 0;
    memset(&handler->recvHeaders, 0, sizeof(handler->recvHeaders));
    handler->recvingState = RECV_STATE_CHUNKHEADER;
    return PRT_CONTINUE;
}

static Http_CallbackResult _WriteHeader(
    HttpClient_SR_SocketData* handler)
{
    char* buf;
    size_t buf_size, sent;
    MI_Result r;

    /* Do we have any data to send? */
    if (!handler->sendHeader)
        return PRT_RETURN_TRUE;

    /* are we done with header? */
    if (handler->sendingState == RECV_STATE_CONTENT)
        return PRT_CONTINUE;

    LOGD2((ZT("_WriteHeader - Begin")));

    buf = ((char*)(handler->sendHeader + 1)) + handler->sentSize;
    buf_size = handler->sendHeader->u.s.size - handler->sentSize;
    sent = 0;

    r = _Sock_Write(handler, buf, buf_size, &sent);
    LOGD2((ZT("_WriteHeader - _Sock_Write result: %d (%s), socket: %d, sent: %d"), (int)r, mistrerror(r), (int)handler->base.sock, (int)sent));

    if (r == MI_RESULT_OK && 0 == sent)
    {
        LOGE2((ZT("_WriteHeader - Connection closed")));
        return PRT_RETURN_FALSE; /* connection closed */
    }

    if (r != MI_RESULT_OK && r != MI_RESULT_WOULD_BLOCK)
    {
        LOGE2((ZT("_WriteHeader - _Sock_Write returned error: %d (%s)"), (int)r, mistrerror(r)));
        return PRT_RETURN_FALSE;
    }

    handler->sentSize += sent;
    handler->headVerb = buf_size > 4 && Strncasecmp(buf, "HEAD", 4) == 0;

    /* did we send all data? */
    if (handler->sentSize != handler->sendHeader->u.s.size)
    {
        LOGD2((ZT("_WriteHeader - Partial write. %u sent this time, %u / %d written, result: %d (%s)"), (unsigned int)sent, (unsigned int)handler->sentSize, (unsigned int)handler->sendHeader->u.s.size, r, mistrerror(r)));
        return PRT_RETURN_TRUE;
    }

    PAL_Free(handler->sendHeader);
    handler->sendHeader = 0;
    handler->sentSize = 0;
    handler->sendingState = RECV_STATE_CONTENT;

    LOGD2((ZT("_WriteHeader - OK exit")));
    return PRT_CONTINUE;
}

static Http_CallbackResult _WriteData(
    HttpClient_SR_SocketData* handler)
{
    char* buf;
    size_t buf_size, sent;
    MI_Result r;

    LOGD2((ZT("_WriteData - Begin")));
    /* are we in the right state? */
    if (handler->sendingState != RECV_STATE_CONTENT)
    {
        LOGE2((ZT("_WriteData - Wrong state. state: %d"), handler->sendingState));
        return PRT_RETURN_FALSE;
    }

    if (!handler->sendPage)
    {   /* no content*/
        handler->sentSize = 0;
        handler->sendingState = RECV_STATE_HEADER;
        handler->base.mask &= ~SELECTOR_WRITE;
        handler->base.mask |= SELECTOR_READ;

        LOGW2((ZT("_WriteData - Content is empty. Continuing")));
        return PRT_CONTINUE;
    }

    buf = ((char*)(handler->sendPage + 1)) + handler->sentSize;
    buf_size = handler->sendPage->u.s.size - handler->sentSize;
    sent = 0;

    r = _Sock_Write(handler, buf, buf_size, &sent);
    LOGD2((ZT("_WriteData - HTTPClient sent %u / %u bytes with result %d (%s)"), (unsigned int)sent, (unsigned int)buf_size, (int)r, mistrerror(r)));

    if (r == MI_RESULT_OK && 0 == sent)
    {
        LOGE2((ZT("_WriteData exit. Connection closed")));
        return PRT_RETURN_FALSE; /* connection closed */
    }

    if (r != MI_RESULT_OK && r != MI_RESULT_WOULD_BLOCK)
    {
        LOGE2((ZT("_WriteData exit - Error: %d (%s)"), r, mistrerror(r)));
        return PRT_RETURN_FALSE;
    }

    handler->sentSize += sent;

    /* did we send all data? */

    if (handler->sentSize != handler->sendPage->u.s.size)
    {
        LOGD2((ZT("_WriteData - Exit. Partial write. %u / %u bytes written"), (unsigned int)handler->sentSize, (unsigned int)handler->sendPage->u.s.size));
        return PRT_RETURN_TRUE;
    }

    LOGD2((ZT("_WriteData - %u / %u bytes sent"), (unsigned int)handler->sentSize, (unsigned int)handler->sendPage->u.s.size));
    PAL_Free(handler->sendPage);
    handler->sendPage = NULL;
    handler->sentSize = 0;
    handler->sendingState = RECV_STATE_HEADER;
    handler->base.mask &= ~SELECTOR_WRITE;
    handler->base.mask |= SELECTOR_READ;

    LOGD2((ZT("_WriteData - OK exit. returning: %d"), PRT_CONTINUE));

    return PRT_CONTINUE;
}

static MI_Boolean _RequestCallbackRead(
    HttpClient_SR_SocketData* handler)
{
    switch (_ReadHeader(handler))
    {
    case PRT_CONTINUE: break;
    case PRT_RETURN_TRUE: return MI_TRUE;
    case PRT_RETURN_FALSE: return MI_FALSE;
    }

    if (handler->recvingState == RECV_STATE_CONTENT)
    {
        switch (_ReadData(handler))
        {
        case PRT_CONTINUE: break;
        case PRT_RETURN_TRUE: return MI_TRUE;
        case PRT_RETURN_FALSE: return MI_FALSE;
        }
    }

    if (handler->recvingState == RECV_STATE_CHUNKHEADER)
    {
        switch (_ReadChunkHeader(handler))
        {
        case PRT_CONTINUE: break;
        case PRT_RETURN_TRUE: return MI_TRUE;
        case PRT_RETURN_FALSE: return MI_FALSE;
        }
    }
    if (handler->recvingState == RECV_STATE_CHUNKDATA)
    {
        switch (_ReadChunkData(handler))
        {
        case PRT_CONTINUE: break;
        case PRT_RETURN_TRUE: return MI_TRUE;
        case PRT_RETURN_FALSE: return MI_FALSE;
        }
    }

    return MI_TRUE;
}

static MI_Boolean _RequestCallbackWrite(
    HttpClient_SR_SocketData* handler)
{
    switch (_WriteHeader(handler))
    {
    case PRT_CONTINUE: break;
    case PRT_RETURN_TRUE: return MI_TRUE;
    case PRT_RETURN_FALSE: return MI_FALSE;
    }

    switch (_WriteData(handler))
    {
    case PRT_CONTINUE: break;
    case PRT_RETURN_TRUE: return MI_TRUE;
    case PRT_RETURN_FALSE: return MI_FALSE;
    }
    return MI_TRUE;
}


static MI_Boolean _RequestCallback(
    Selector* sel,
    Handler* handlerIn,
    MI_Uint32 mask,
    MI_Uint64 currentTimeUsec)
{
    HttpClient_SR_SocketData* handler = (HttpClient_SR_SocketData*)handlerIn;
    MI_UNUSED(sel);

    if (((mask & SELECTOR_READ) != 0 && !handler->reverseOperations) ||
        ((mask & SELECTOR_WRITE) != 0 && handler->reverseOperations))
    {
        if (!_RequestCallbackRead(handler))
        {
            LOGE2((ZT("_RequestCallback - RequestCallbackRead failed")));
            return MI_FALSE;
        }
        LOGD2((ZT("_RequestCallback - Called _RequestCallbackRead. %u / %u bytes read"), (unsigned int)handler->receivedSize, handler->recvPage == NULL ? 0 : (unsigned int)handler->recvPage->u.s.size));
    }

    if (((mask & SELECTOR_WRITE) != 0 && !handler->reverseOperations) ||
        ((mask & SELECTOR_READ) != 0 && handler->reverseOperations))
    {
        if (!_RequestCallbackWrite(handler))
        {
            LOGE2((ZT("_RequestCallback - _RequestCallbackWrite failed")));
            return MI_FALSE;
        }
        LOGD2((ZT("_RequestCallback - Called _RequestCallbackWrite. %u / %u bytes sent"), (unsigned int)handler->sentSize, handler->sendPage == NULL ? 0 : (unsigned int)handler->sendPage->u.s.size));
        while (handler->sendPage != NULL && handler->sentSize < handler->sendPage->u.s.size)
        {                               /* assume 500 bytes per millisecond transmission */
                                        /* wait after to avoid spinning too much on _WriteData */
            unsigned int bytesLeft = (unsigned int)handler->sendPage->u.s.size - (unsigned int)handler->sentSize;
            unsigned long msec = (unsigned long)(bytesLeft / 500 + 1);

            LOGD2((ZT("_RequestCallback - Called _WriteData. %u / %u bytes sent"), (unsigned int)handler->sentSize, handler->sendPage == NULL ? 0 : (unsigned int)handler->sendPage->u.s.size));
            if (_WriteData(handler) == MI_FALSE)
            {
                LOGE2((ZT("_RequestCallback - _WriteData failed")));
                return MI_FALSE;
            }
            LOGD2((ZT("_RequestCallback - Called _WriteData. %u bytes written, %u bytes left"), (unsigned int)handler->sentSize, handler->sendPage == NULL ? 0 : (unsigned int)handler->sendPage->u.s.size));
            Sleep_Milliseconds(msec);
        }
        LOGD2((ZT("_RequestCallback - Called _RequestCallbackWrite. %u / %u bytes sent"), (unsigned int)handler->sentSize, handler->sendPage == NULL ? 0 : (unsigned int)handler->sendPage->u.s.size));
    }

    /* re-set timeout - if we performed R/W operation, set timeout depending where we are in communication */
    if (mask & (SELECTOR_READ | SELECTOR_WRITE))
    {
        handler->base.fireTimeoutAt = currentTimeUsec + handler->timeoutUsec;
    }

    /* Close connection by timeout */
    if ((mask & SELECTOR_TIMEOUT) != 0)
    {
        if (handler->status != MI_RESULT_OK)
        {
            handler->recvHeaders.httpError = 408;
            handler->status = MI_RESULT_TIME_OUT;
        }
        LOGE2((ZT("_RequestCallback - Timed out. socket: %d, result: %d (%s)"), handler->base.sock, handler->status, mistrerror(handler->status)));
        return MI_FALSE;
    }

    if ((mask & SELECTOR_REMOVE) != 0 || (mask & SELECTOR_DESTROY) != 0)
    {
        HttpClient* self = (HttpClient*)handler->base.data;

        /* notify next stack layer */
        if (handler->status != MI_RESULT_OK)
            (*self->callbackOnStatus)(self, self->callbackData, handler->status);

        /* Yeah, this is hokey, but we need to sleep here to let the */
		/* subsystems have the opportunity to send the data before we close */
		/* the socket, or we'll get a broken pipe/connection reset */
#if defined(CONFIG_OS_WINDOWS)
        Sleep_Milliseconds(1);
#else
        usleep(50);
#endif

        if (handler == NULL)
        {
            LOGE2((ZT("_RequestCallback - The handler object was free'd under us!")));
            return MI_TRUE;
        }

        self->connector = NULL;

        if (handler->ssl)
            SSL_free(handler->ssl);

        Sock_Close(handler->base.sock);

        if (handler->recvPage)
            PAL_Free(handler->recvPage);

        if (handler->sendPage)
            PAL_Free(handler->sendPage);

        if (handler->sendHeader)
            PAL_Free(handler->sendHeader);

        PAL_Free(handler->recvBuffer);
        PAL_Free(handler);
    }

    return MI_TRUE;
}

#ifdef CONFIG_POSIX

/*
 Verify callback when the server authentication certificate's chain of trust is checked. This
 is the same as the Open SSL verify callback function (return preverify_ok), except that it
 logs a diagnostic message when preverify_ok has a failed status.
*/
static int _ctxVerify(
    int preverify_ok,
    X509_STORE_CTX* ctx)
{
    if (preverify_ok <= 0)
    {
        X509* certWithError = X509_STORE_CTX_get_current_cert(ctx);
        int error = X509_STORE_CTX_get_error(ctx);
        char nameBuf[256];

        X509_NAME_oneline(X509_get_subject_name(certWithError), nameBuf, 256);
        trace_SSL_VerifyFailed(error, nameBuf);
    }
    return preverify_ok;
}

/*
 Create an Open SSL context that will be used for secure communication. Set up server and client
 certificate authentication if specified.
*/
static MI_Result _CreateSSLContext(
    HttpClient* self,
    const char* trustedCertsDir,
    const char* certFile,
    const char* privateKeyFile)
{
    SSL_CTX* sslContext = SSL_CTX_new(SSLv23_method());
    if (sslContext == NULL)
    {
        LOGE2((ZT("_CreateSSLContext - Cannot create SSL context")));
        trace_SSL_CannotCreateContext();
        return MI_RESULT_FAILED;
    }
    SSL_CTX_set_quiet_shutdown(sslContext, 1);
    (void)SSL_CTX_set_mode(sslContext, SSL_MODE_AUTO_RETRY | SSL_MODE_ENABLE_PARTIAL_WRITE);
    SSL_CTX_set_session_cache_mode(sslContext, SSL_SESS_CACHE_OFF);
    SSL_CTX_sess_set_remove_cb(sslContext, NULL);

    if (trustedCertsDir != NULL)
    {
        /* Cause Open SSL to check the server certificate Subject against its FQDN and
        ** to check the server certificate chain against the contents of *trustedCertsDir.
        */
        if (SSL_CTX_load_verify_locations(sslContext, NULL, trustedCertsDir) < 0)
        {
            LOGE2((ZT("_CreateSSLContext - Cannot set directory containing trusted certificate(s) to %s"), trustedCertsDir));
            trace_SSL_BadTrustDir(trustedCertsDir);
        }
        SSL_CTX_set_verify(sslContext, SSL_VERIFY_PEER, _ctxVerify);
    }

    /* Check if there is a client certificate file (file containing client authentication
    ** certificate) specified. If specified, validate and load the certificate.
    */
    if (certFile != NULL && *certFile != '\0')
    {
        int err;

        /* load the specified client certificates */
        LOGD2((ZT("_CreateSSLContext - Loading server certificate from: %s"), certFile));

        err = SSL_CTX_use_certificate_file(sslContext,
                                           certFile,
                                           strcmp(certFile + strlen(certFile) - 4, ".pem") == 0 ? SSL_FILETYPE_PEM :  SSL_FILETYPE_ASN1);
        if (err <= 0)
        {
#if defined(ENABLE_TRACING)
            unsigned long error = ERR_peek_last_error();
#endif

            LOGE2((ZT("_CreateSSLContext - No client certificate found in %s"), certFile));
            LOGE2((ZT("_CreateSSLContext - OpenSSL Error 0x%lX (%s) in SSL_CTX_use_certificate_file"), error, sslstrerror(error)));
            SSL_CTX_free(sslContext);
            sslContext = NULL;
            return MI_RESULT_FAILED;
        }

        if (privateKeyFile != NULL && *privateKeyFile != '\0')
        {
            /* load the specified private key */
            LOGD2((ZT("_CreateSSLContext - SSL Loading client private key from: %s"), privateKeyFile));

            err = SSL_CTX_use_RSAPrivateKey_file(sslContext,
                                                 privateKeyFile,
                                                 strcmp(privateKeyFile + strlen(privateKeyFile) - 4, ".pem") == 0 ? SSL_FILETYPE_PEM :  SSL_FILETYPE_ASN1);
            if (err <= 0)
            {
#if defined(ENABLE_TRACING)
                unsigned long error = ERR_peek_last_error();
#endif
                LOGE2((ZT("_CreateSSLContext - Invalid private key found in %s"), privateKeyFile));
                LOGE2((ZT("_CreateSSLContext - OpenSSL error 0x%lX (%s) in SSL_CTX_use_PrivateKey_file"), error, sslstrerror(error)));
                SSL_CTX_free(sslContext);
                sslContext = NULL;
                return MI_RESULT_FAILED;
            }
        }
    }

    self->sslContext = sslContext;
    return MI_RESULT_OK;
}

#endif

static MI_Result _CreateSocketAndConnect(
    Sock* s,
    Addr* addr)
{
    MI_Result r;

    LOGD2((ZT("_CreateSocketAndConnect - Begin")));

    /* Create client socket. */
    r = Sock_Create(s, addr->is_ipv6);
    if (r != MI_RESULT_OK)
    {
        LOGE2((ZT("_CreateSocketAndConnect - Sock_Create failed. result: %d (%s)"), r, mistrerror(r)));
        return r;
    }

    /* set the socket to be non-blocking */
    r = Sock_SetBlocking(*s, MI_FALSE);
    if (r != MI_RESULT_OK)
    {
        LOGE2((ZT("_CreateSocketAndConnect - Sock_SetBlocking failed. result: %d (%s)"), r, mistrerror(r)));
        return r;
    }

    /* connect the socket to the IP address */
    r = Sock_Connect(*s, addr);
    if (r != MI_RESULT_OK)
    {
        LOGE2((ZT("_CreateSocketAndConnect - Sock_Connect failed. result: %d (%s)"), r, mistrerror(r)));
        return r;
    }

    LOGD2((ZT("_CreateSocketAndConnect - OK exit")));

    return MI_RESULT_WOULD_BLOCK;
}

static MI_Result _CreateConnectorSocket(
    HttpClient* self,
    const char* host,
    unsigned short port,
    MI_Boolean secure)
{
    Addr addr;
    MI_Result r;
    Sock s;
    HttpClient_SR_SocketData* h;
    MI_Uint64 currentTimeUsec;

    LOGD2((ZT("_CreateConnectorSocket - Begin. host: %s, port: %d, secure? %d"), host, port, secure));

    /* timeout calculation */
    if (PAL_Time(&currentTimeUsec) != PAL_TRUE)
    {
        LOGE2((ZT("_CreateConnectorSocket - PAL_Time failed")));
        return MI_RESULT_FAILED;
    }

    /* This code tries to connect using the preferred addressing family (IPv4 */
	/* or IPv6). If that fails and Addr_Init has a secondary addressing */
	/* family, a connection with the secondary family, it tries using the */
	/* secondary family next. */

    /* Initialize preferred address */
    r = Addr_Init(&addr, host, port, MI_FALSE);
    if (r != MI_RESULT_OK)
    {
        LOGE2((ZT("_CreateConnectorSocket - Addr_Init failed. result: %d (%s)"), r, mistrerror(r)));
        return r;
    }

    /* Connect to the server */
    r = _CreateSocketAndConnect(&s, &addr);
    if (r != MI_RESULT_OK && r != MI_RESULT_WOULD_BLOCK)
    {
        MI_Result r2;

        Sock_Close(s);

        LOGW2((ZT("_CreateConnectorSocket - _CreateSocketAndConnect of primary address failed. result: %d (%s)"), r, mistrerror(r)));

        /* Initialize secondary address */
        r2 = Addr_Init(&addr, host, port, MI_TRUE);
        if (r2 != MI_RESULT_OK)
            return r;                   /* on error, return original failure */
        r2 = _CreateSocketAndConnect(&s, &addr);
        if (r2 != MI_RESULT_OK && r2 != MI_RESULT_WOULD_BLOCK)
        {
            Sock_Close(s);

            LOGE2((ZT("_CreateConnectorSocket - Addr_Init failed. result: %d (%s)"), r, mistrerror(r)));

            return r;                   /* on error, return original failure */
        }
        r = r2;
    }

    /* Create handler */
    h = (HttpClient_SR_SocketData*)PAL_Calloc(1, sizeof(HttpClient_SR_SocketData));

    if (h == NULL)
    {
        Sock_Close(s);
        LOGE2((ZT("_CreateConnectorSocket - calloc failed")));
        return MI_RESULT_FAILED;
    }

    h->recvBufferSize = INITIAL_BUFFER_SIZE;
    h->recvBuffer = (char*)PAL_Calloc(1, h->recvBufferSize);
    if (!h->recvBuffer)
    {
        PAL_Free(h);
        Sock_Close(s);
        LOGE2((ZT("_CreateConnectorSocket - calloc failed")));
        return MI_RESULT_FAILED;
    }

    h->base.sock = s;
    h->base.mask = SELECTOR_EXCEPTION;
    h->base.callback = _RequestCallback;
    h->base.data = self;
    h->timeoutUsec = DEFAULT_HTTP_TIMEOUT_USEC;
    h->base.fireTimeoutAt = currentTimeUsec + h->timeoutUsec;

    /* SSL support */
    if (secure)
    {
        h->ssl = SSL_new(self->sslContext);

        if (!h->ssl)
        {
            LOGW2((ZT("_CreateConnectorSocket - SSL_new failed")));
            trace_SSLNew_Failed();
            PAL_Free(h);
            Sock_Close(s);
            return MI_RESULT_FAILED;
        }

        Sock_SetBlocking(s, MI_TRUE);
        if (!(SSL_set_fd(h->ssl, s) ))
        {
            LOGW2((ZT("_CreateConnectorSocket - SSL_set_fd failed")));
            trace_SSL_setfd_Failed();
            SSL_free(h->ssl);
            PAL_Free(h);
            Sock_Close(s);
            return MI_RESULT_FAILED;
        }

        SSL_set_connect_state(h->ssl);
    }

    /* Watch for read events on the incoming connection */
    r = Selector_AddHandler(self->selector, &h->base);

    if (r != MI_RESULT_OK)
    {
        LOGE2((ZT("_CreateConnectorSocket - Selector_AddHandler failed with error: %d (%s)"), (int)r, mistrerror(r)));
        trace_SelectorAddHandler_Failed();
        if (secure)
            SSL_free(h->ssl);
        PAL_Free(h);
        Sock_Close(s);
        return MI_RESULT_FAILED;
    }

    self->connector = h;

    LOGD2((ZT("_CreateConnectorSocket - OK exit. socket: %d, secure: %d, timeout: %s"), h->base.sock, secure, FmtInterval(h->base.fireTimeoutAt - currentTimeUsec)));

    return MI_RESULT_OK;
}

static MI_Result _New_Http(
    HttpClient** selfOut,
    Selector* selector, /*optional, maybe NULL*/
    HttpClientCallbackOnStatus statusCallback,
    HttpClientCallbackOnResponse  responseCallback,
    void* callbackData)
{
    HttpClient* self;

    /* Check parameters */
    if (!selfOut)
        return MI_RESULT_INVALID_PARAMETER;

    /* Clear output parameter */
    *selfOut = NULL;

    /* Allocate structure */
    {
        self = (HttpClient*)PAL_Calloc(1, sizeof(HttpClient));

        if (!self)
            return MI_RESULT_FAILED;
    }

    if (selector)
    {   /* attach the exisiting selector */
        self->selector = selector;
        self->internalSelectorUsed = MI_FALSE;
    }
    else
    {   /* creaet a new selector */
        /* Initialize the network */
        Sock_Start();

        /* Initialize the selector */
        if (Selector_Init(&self->internalSelector) != MI_RESULT_OK)
        {
            PAL_Free(self);
            LOGE2((ZT("_NewHttp - Selector_Init failed")));
            return MI_RESULT_FAILED;
        }
        self->selector = &self->internalSelector;
        self->internalSelectorUsed = MI_TRUE;
    }

    /* Save the callback and callbackData */
    self->callbackOnResponse = responseCallback;
    self->callbackOnStatus = statusCallback;
    self->callbackData = callbackData;

    /* Set the magic number */
    self->magic = _MAGIC;

    /* Set output parameter */
    *selfOut = self;
    return MI_RESULT_OK;
}



static size_t _GetHeadersSize(
    const HttpClientRequestHeaders* headers)
{
    size_t res = 0;
    size_t index = 0;

    while (index < headers->size)
    {
        res += Strlen(headers->data[index]);
        res += 2; /* \r \n pair */
        index++;
    }

    return res;
}

static Page* _CreateHttpHeader(
    const char* verb,
    const char* uri,
    const HttpClientRequestHeaders* headers,
    size_t size)
{
    Page* page = 0;
    size_t pageSize = 0;
    int r;
    char* p;

#define HTTP_HEADER_FORMAT "%s %s HTTP/1.1\r\n" \
    "Content-Length: %d\r\n"\
    "Connection: Keep-Alive\r\n" \
    "Host: host\r\n"

#define HTTP_HEADER_FORMAT_NOCL "%s %s HTTP/1.1\r\n" \
    "Connection: Keep-Alive\r\n" \
    "Host: host\r\n"

    /* calculate approximate page size */
    if (!verb)
        verb = "POST";

    pageSize += sizeof(HTTP_HEADER_FORMAT) + 10; /* format + 10 digits of content length */

    if (SizeTAdd(pageSize, Strlen(verb), &pageSize) != S_OK ||
        SizeTAdd(pageSize, Strlen(uri),  &pageSize) != S_OK ||
        SizeTAdd(pageSize, sizeof(Page), &pageSize) != S_OK ||
        (headers && SizeTAdd(pageSize, _GetHeadersSize(headers), &pageSize)) != S_OK)
    {
        // Overflow
        return 0;
    }

    page = (Page*)PAL_Malloc(pageSize);

    if (!page)
        return 0;

    /* clear header */
    memset(page, 0, sizeof(Page));

    p = (char*)(page + 1);

    if (size)
        r = Snprintf(p, pageSize, HTTP_HEADER_FORMAT, verb, uri, (int)size);
    else
        r = Snprintf(p, pageSize, HTTP_HEADER_FORMAT_NOCL, verb, uri);

    if (r < 0)
    {
        PAL_Free(page);
        return 0;
    }

    p += r;
    pageSize -= r;

    if (headers)
    {
        size_t index = 0;

        while (index < headers->size)
        {
            r = (int)Strlcpy(p,headers->data[index], pageSize);
            p += r;
            pageSize -= r;
            r = (int)Strlcpy(p,"\r\n", pageSize);
            p += r;
            pageSize -= r;

            index++;
        }
    }

    /* add trailing \r\n */
    r = (int)Strlcpy(p,"\r\n", pageSize);
    p += r;
    pageSize -= r;

    page->u.s.size = (unsigned int)(p - (char*)(page+1));

    return page;
}

/* ************************************************************************* *\
                                HTTP CLIENT
\* ************************************************************************* */
/*
    Creates new http client.

    Parameters:
    selfOut - [out] newly created http object (or null if failed).
    selector - [opt] selector to use for socket monitoring. If selector not specified,
            private one is created.
    host - host address
    port - port number
    secure - flag that indicates if http or https conneciton is required

    Returns:
    'OK' on success or error code otherwise
*/
MI_Result HttpClient_New_Connector(
    HttpClient** selfOut,
    Selector* selector, /*optional, maybe NULL*/
    const char* host,
    unsigned short port,
    MI_Boolean secure,
    HttpClientCallbackOnStatus statusCallback,
    HttpClientCallbackOnResponse  responseCallback,
    void* callbackData,
    const char* trustedCertsDir,
    const char* certFile,
    const char* privateKeyFile)
{
    HttpClient* self;
    MI_Result r;

    /* allocate this, inits selector */
    r = _New_Http(selfOut, selector, statusCallback,
                  responseCallback, callbackData);

    if (MI_RESULT_OK != r)
    {
        LOGE2((ZT("HttpClient_New_Connector - _New_Http failed. result: %d (%s)"), r, mistrerror(r)));
        return r;
    }
    self = *selfOut;

#ifdef CONFIG_POSIX
    /* Allocate SSL context */
    if (secure)
    {
        /* init ssl */
        SSL_library_init();

        /* create context */
        r = _CreateSSLContext(self, trustedCertsDir, certFile, privateKeyFile);

        if (r != MI_RESULT_OK)
        {
            HttpClient_Delete(self);
            *selfOut = NULL;
            LOGE2((ZT("HttpClient_New_Connector - _CreateSSLContext failed. result: %d (%s)"), r, mistrerror(r)));
            return r;
        }
    }
#else
    MI_UNUSED(secure);
#endif

    /* Create http connector socket */
    {
        r = _CreateConnectorSocket(self, host, port, secure);

        if (r != MI_RESULT_OK)
        {
            HttpClient_Delete(self);
            LOGE2((ZT("HttpClient_New_Connector - _CreateConnectorSocket failed failed. result: %d (%s)"), r, mistrerror(r)));
            return r;
        }
    }

    return MI_RESULT_OK;
}

/*
    Deletes http object, disconnects form the server
    and frees all related resources.

    Parameters:
    self - http object

    Returns:
    OK
*/
MI_Result HttpClient_Delete(
    HttpClient* self)
{
    /* Check parameters */
    if (!self)
        return MI_RESULT_INVALID_PARAMETER;

    /* Check magic number */
    if (self->magic != _MAGIC)
    {
        LOGE2((ZT("HttpClient_Delete - Bad magic number")));
        return MI_RESULT_INVALID_PARAMETER;
    }

    if (self->internalSelectorUsed)
    {
        /* Release selector;
        Note: Selector_Destroy closes all sockets in a list including connector and listener */
        Selector_Destroy(self->selector);

        /* Shutdown the network */
        Sock_Stop();
    }
    else
    {
        /* remove connector from handler */
        if (self->connector)
            Selector_RemoveHandler(self->selector, &self->connector->base);
    }

    if (self->sslContext)
        SSL_CTX_free(self->sslContext);

    /* Clear magic number */
    self->magic = 0xDDDDDDDD;

    /* Free self pointer */
    PAL_Free(self);

    return MI_RESULT_OK;
}


/*
    Sends http request.

    Parameters:
    self - http object
    uri - request's URI
    headers - [opt] extra headers for request.
    data - [opt] content to send. if message is accepted to be sent,
        on return *data == null (taking memory ownership)

    Returns:
    OK or appropriate error
 */
MI_Result HttpClient_StartRequest(
    HttpClient* self,
    const char* verb,
    const char* uri,
    const HttpClientRequestHeaders* headers,
    Page** data)
{
    LOGD2((ZT("HttpClient_StartRequest - Begin. verb: %s, URI: %s"), verb, uri));

    /* check params */
    if (!self || !uri)
        return MI_RESULT_INVALID_PARAMETER;

    if (self->magic != _MAGIC)
    {
        LOGE2((ZT("HttpClient_Delete - Bad magic number")));
        trace_StartRequest_InvalidMagic();
        return MI_RESULT_INVALID_PARAMETER;
    }

    if (self->connector == NULL)
    {
        LOGE2((ZT("HttpClient_Delete - Connection is not open")));
        trace_StartRequest_ConnectionClosed();
        return MI_RESULT_FAILED;
    }

    /* create header page */
    self->connector->sendHeader =
        _CreateHttpHeader(verb, uri, headers, (data && *data) ? (*data)->u.s.size : 0);

    if (data != NULL)
    {
        self->connector->sendPage = *data;
        *data = 0;
    }
    else
        self->connector->sendPage = NULL;

    /* set status to failed, until we know more details */
    self->connector->status = MI_RESULT_FAILED;
    self->connector->sentSize = 0;
    self->connector->sendingState = RECV_STATE_HEADER;
    self->connector->base.mask |= SELECTOR_WRITE;

    _RequestCallbackWrite(self->connector);

    return MI_RESULT_OK;
}

MI_Result HttpClient_Run(
    HttpClient* self,
    MI_Uint64 timeoutUsec)
{
    /* Run the selector */
    return Selector_Run(self->selector, timeoutUsec, MI_FALSE);
}

MI_Result HttpClient_SetTimeout(
    HttpClient* self,
    MI_Uint64 timeoutUsec)
{
    MI_Uint64 currentTimeUsec = 0;

    PAL_Time(&currentTimeUsec);

    /* check params */
    if (!self)
        return MI_RESULT_INVALID_PARAMETER;

    if (self->magic != _MAGIC)
    {
        trace_Timeout_InvalidMagic();
        return MI_RESULT_INVALID_PARAMETER;
    }

    if (!self->connector)
        return MI_RESULT_INVALID_PARAMETER;

    /* create header page */
    self->connector->timeoutUsec = timeoutUsec;
    self->connector->base.fireTimeoutAt = currentTimeUsec + self->connector->timeoutUsec;

    return MI_RESULT_OK;
}

ViewCVS 0.9.2