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

File: [OMI] / omi / protocol / Attic / http.c (download)
Revision: 1.1.1.1 (vendor branch), Wed May 30 21:47:49 2012 UTC (12 years, 1 month ago) by mike
Branch: TOG
CVS Tags: OMI_1_0_2_Branch, OMI_1_0_2, OMI_1_0_1_PRE, OMI_1_0_0
Changes since 1.1: +0 -0 lines
Initial Import

/*
**==============================================================================
**
** 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 "http.h"
#include "addr.h"
#include "sock.h"
#include "selector.h"
#include <base/time.h>
#include <base/buf.h>
#include <base/log.h>
#include <base/result.h>
#include <base/strings.h>
#include <base/io.h>
#include <base/paths.h>

#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_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

#endif

#define T MI_T

/* #define ENABLE_TRACING  // */
#ifdef  ENABLE_TRACING
#define PRINTF(a)  TIMESTAMP(); printf a
#define TIMESTAMP() \
{\
        MI_Uint64 currentTimeUsec = 0;\
\
        Time_Now(&currentTimeUsec);\
        currentTimeUsec /= 1000;    /* ms */ \
        printf("%ds%03dms ", (int)(currentTimeUsec / 1000 % 1000), (int)(currentTimeUsec % 1000));\
}

#define PRINTF_2(a)
#else
#define PRINTF(a)
#define PRINTF_2(a)
#endif



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

static const MI_Uint32 _MAGIC = 0xE0BB5FD3;
static const MI_Uint32 MAX_HEADER_SIZE = 2 * 1024;
static const MI_Uint32 INITIAL_BUFFER_SIZE = 2 * 1024;
static const size_t HTTP_MAX_CONTENT = 1024 * 1024;

struct _Http
{
    MI_Uint32           magic;
    Selector        internalSelector;
    Selector*       selector;
    HttpCallbackOnNewConnection     callbackOnNewConnection;
    HttpCallbackOnCloseConnection   callbackOnCloseConnection;
    HttpCallbackOnRequest           callbackOnRequest;
    void*                               callbackData;
    SSL_CTX*    sslContext;
    /* options: timeouts etc */
    HttpOptions     options;
    MI_Boolean  internalSelectorUsed;
};

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

    MI_Boolean secure;
}
Http_Listener_SocketData;

typedef enum _Http_RecvState
{
    RECV_STATE_HEADER,
    RECV_STATE_CONTENT
}
Http_RecvState;

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

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

    /* is server/provider is processing request 
        (to disbale timeout) */
    MI_Boolean  requestIsBeingProcessed;

    /* link to next stack layer */
    void*  connectionData;

    /* receiving data */
    char*       recvBuffer;
    size_t      recvBufferSize;
    size_t      recvievedSize;
    Http_RecvState  recvingState;
    HttpHeaders recvHeaders;
    Page*   recvPage;

    /* sending part */
    Page*   sendPage;
    size_t      sentSize;
    Http_RecvState  sendingState;
    int         httpErrorCode;
}
Http_SR_SocketData;

/* helper functions result */
typedef enum _Http_CallbackResult
{
    PRT_CONTINUE,
    PRT_RETURN_TRUE,
    PRT_RETURN_FALSE
}
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)) )

static MI_Boolean _DecodeBasicAuth(
    HttpHeaders* recvHeaders,
    const char * src,
    char* tgt)
{
    /* decoding array 0-63 - valid value, 64-padding, 65-skip, -1-invalid */
    static const char s_decodingArray[] = 
    {
        /* 0 - 31 */
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 

        /* 32 - 39  !"#$%&' */
        -1, -1, -1, -1, -1, -1, -1, -1, 

        /* ()*+,-./ */
        -1, -1, -1, 62,  -1, -1, -1, 63,   

        /* 0 -9 */
        52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 

        /* :;<=>? */
        -1,-1,-1,64,-1,-1,

        /* @ A-Z */
        -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 
        15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 

        /* []\\^_` */
        -1,-1,-1,-1,-1,-1,

        /* a-z */
        26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 
        42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 

        /* {|} ~ 127 */
        -1, -1, -1, -1, -1
    };

    char* startBuffer = tgt;

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

    /* perform decoding - 4 input bytes are converted into 3 output, 
        using porvided pointers src/tgt
        since decoding is done inplace and result is smaller than input, 
        no checks for buffer size is required */
    while (*src)
    {   
        signed char b1, b2, b3, b4;

        b1 = s_decodingArray[(*src++) & 0x7f];
        if (b1 < 0 )
            return MI_FALSE;
        b2 = s_decodingArray[(*src++) & 0x7f];
        if (b2 < 0 )
            return MI_FALSE;
        b3 = s_decodingArray[(*src++) & 0x7f];
        if (b3 < 0 )
            return MI_FALSE;
        b4 = s_decodingArray[(*src++) & 0x7f];
        if (b4 < 0 )
            return MI_FALSE;

        *tgt++ = (b1 << 2) | (b2 >> 4);

        if (b3 != 64)
            *tgt++ = ((b2&0xF) << 4) | (b3 >> 2);

        if (b4 != 64)
            *tgt++ = ((b3&0x3) << 6) | (b4);
    }
    *tgt = 0;

    /* decoded string has format:
        <uersname>[:<password>] */
    recvHeaders->username = startBuffer;
    startBuffer = strchr(startBuffer, ':');

    if ( startBuffer )
    {
        *startBuffer = 0;
        recvHeaders->password = startBuffer + 1;
    }

    return MI_TRUE; 
}

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

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

    for (len = 1; (*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 ( ; ; )
    {
        if (p[0] == '\r' && p[1] == '\n' && 
            (p[2] != ' ' && p[2] != '\t') )
        {
            p[0] = 0;
            (*line) = p + 2;
            break;
        }
        p ++;
    }

    /* remove trailing spaces */
    p--;
    while (p[0] == ' ' || p[0] == '\t')
        p--;

    p[1] = 0;

    return MI_TRUE;
}

static void _ParseContentType(
    HttpHeaders* recvHeaders,
    char* value)
{
    recvHeaders->contentType = value;

    /* find attribute list */
    while (value[0] != 0 && value[0] != ';')
        value++;

    /* Check if attribute list was provided */
    if (value[0] == 0)
        return;

    /* terminate type/subtype; move to attributes list */
    value[0] = 0;
    value++;

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

    /* find charset attribute (if present) */
    if (Strncasecmp(value,"charset=",8) != 0)
        return;

    value += 8;
    recvHeaders->charset = value;

    /* can be enclosed in quotes */
    if (value[0] == '"')
    {
        /* skip '"' */
        recvHeaders->charset++;
        value++;
        value = strchr(value, '"');
        if (value)
            *value = 0;
        else
            recvHeaders->charset = 0;
    }
    else
    {
        /*skip trialing spaces */
        while (value[0] != 0 && value[0] != ' ' && value[0] != '\t')
            value++;

        *value = 0;
    }
}

static MI_Boolean _getHeaderField(
    Http_SR_SocketData* handler,
    char ** line)
{
    char* name = *line;
    char* value = NULL;
    int nameHashCode;

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


    switch (nameHashCode)
    {
    case (_HashCode('c','e',12)): /*Content-Type*/
        if (Strcasecmp(name,"Content-Type") == 0)
            _ParseContentType(&handler->recvHeaders, value);

        break;

    case (_HashCode('c','h',14)): /*Content-Length*/
        if (Strcasecmp(name,"Content-Length") == 0)
        {
            handler->recvHeaders.contentLength = (size_t)Strtoull(value, NULL, 10);
            if ( handler->recvHeaders.contentLength > HTTP_MAX_CONTENT )
                return MI_FALSE;
        }
        break;

    case (_HashCode('a','n',13)): /*Authorization*/
        if (Strcasecmp(name,"Authorization") == 0)
        {
            if (Strncasecmp(value,"Basic",5) == 0)
            {
                /* since decoded string is smaller, performing decoding inplace;
                    for source skip 'Basic ' part (6 chars) */
                if (!_DecodeBasicAuth(&handler->recvHeaders, value + 6, value))
                {
                    LOGW_CHAR(("base64 decoding error in Basic auth: [%s]\n", value));
                    return MI_FALSE;
                }
            }
            else    /* unknown authorization type */
                handler->recvHeaders.authorization = value;
        }
        break;

    default:
        break;

    }

    return MI_TRUE;
}

static MI_Boolean _getRequestLine(
    Http_SR_SocketData* handler,
    char ** 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
    */

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

    return MI_FALSE;
}

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

    if (!handler->ssl)
        return Sock_Read(handler->base.sock, buf, buf_size, sizeRead);

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

    *sizeRead = 0;

    if (handler->acceptDone)
    {
        res = SSL_read(handler->ssl, buf, buf_size);
        PRINTF(("ssl read %d\n", res));
    }
    else
    {
        res = SSL_accept(handler->ssl);
        PRINTF(("ssl accept %d\n", res));
        if ( res > 0 )
        {
            /* we are done with accpet */
            handler->acceptDone = MI_TRUE;
            return _Sock_Read(handler,buf,buf_size,sizeRead);
        }
        /* perform regular error checking */
    }

    if ( res == 0 )
        return MI_RESULT_OK;    /* connection closed */

    if ( res > 0 )
    {
        *sizeRead = res;
        return MI_RESULT_OK;    /* ok */
    }

    switch (SSL_get_error(handler->ssl, res))
    {
    case SSL_ERROR_WANT_WRITE:
        handler->reverseOperations = MI_TRUE;   /* wait until write is allowed */
        handler->base.mask |= SELECTOR_WRITE;
        handler->base.mask &= ~SELECTOR_READ;
        PRINTF(("ssl read/accept WANT_WRITE\n"));
        return MI_RESULT_WOULD_BLOCK;

    case SSL_ERROR_WANT_READ:
        PRINTF(("ssl read/accept WANT_READ\n"));
        return MI_RESULT_WOULD_BLOCK;

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

        LOGW_CHAR(("ssl-read: unexpected sys error %d\n", errno));
        break;

    default:
        {
            /* print error */
            unsigned long err = ERR_get_error();
            while (err)
            {
                char err_txt[200];
                ERR_error_string_n(err, err_txt, sizeof(err_txt));

                LOGW_CHAR(("ssl-read error: %d [%s]\n", (int)err, err_txt));

                err = ERR_get_error();
            }
        }
        break;
    }
    return MI_RESULT_FAILED;
}

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

    if (!handler->ssl)
        return Sock_Write(handler->base.sock, buf, buf_size, sizeWritten);

    /* 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;
    res = SSL_write(handler->ssl, buf, buf_size);
    PRINTF(("ssl write %d\n", res));

    if ( res == 0 )
        return MI_RESULT_OK;    /* connection closed */

    if ( res > 0 )
    {
        *sizeWritten = res;
        return MI_RESULT_OK;    /* ok */
    }

    switch (SSL_get_error(handler->ssl, res))
    {
    case SSL_ERROR_WANT_WRITE:
        return MI_RESULT_WOULD_BLOCK;

    case SSL_ERROR_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)
            return MI_RESULT_WOULD_BLOCK;

        LOGW_CHAR(("ssl-write: unexpected sys error %d\n", errno));
        break;

    default:
        break;
    }
    return MI_RESULT_FAILED;
}

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

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

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

    r = _Sock_Read(handler, buf, buf_size, &received);
    PRINTF(("%d: read r = %d, recv = %d; reverse %d\n", (int)handler->base.sock, (int)r, (int)received, (int)handler->reverseOperations ));

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

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

    if (!received)
        return PRT_RETURN_TRUE;

    handler->recvievedSize += received;

    /* check header */
    PRINTF_2(("%s\n",buf));

    /* did we get full header? */
    buf = handler->recvBuffer;
    for ( index = 3; index < handler->recvievedSize; index++ )
    {
        if (buf[index-3] == '\r' && buf[index-1] == '\r' &&
            buf[index-2] == '\n' && buf[index] == '\n' )
        {
            fullHeaderReceived = MI_TRUE;
            break;
        }
    }

    if (!fullHeaderReceived )
    {
        if ( handler->recvievedSize <  handler->recvBufferSize )
            return PRT_RETURN_TRUE; /* continue reading */

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

            if (!buf)
                return PRT_RETURN_FALSE;

            handler->recvBufferSize *= 2;
            handler->recvBuffer = buf;
            return _ReadHeader(handler);
        }
        else
        {
            /* http header is too big - drop connection */
            LOGW((T("http header is too big; dropping connection\n")));
            return PRT_RETURN_FALSE;
        }
    }

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

    if (!_getRequestLine(handler, &currentLine))
        return PRT_RETURN_FALSE;


    while ((data-currentLine) > 3)
    {
        if (!_getHeaderField(handler, &currentLine))
            return PRT_RETURN_FALSE;

    }

    /* Allocate zero-terminated buffer */
    handler->recvPage = (Page*)malloc(sizeof(Page) + handler->recvHeaders.contentLength + 1);

    if (!handler->recvPage)
        return PRT_RETURN_FALSE;

    ((char*)(handler->recvPage + 1))[handler->recvHeaders.contentLength] = 0;

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

    handler->recvievedSize -= index + 1;

    /* Verify that we have not more than 'content-length' bytes in buffer left 
        If we hvae more, assuming http client is invalid and drop connection */
    if (handler->recvievedSize > handler->recvHeaders.contentLength)
    {
        LOGW((T("http payload is bigger than content-length\n")));
        return PRT_RETURN_FALSE;
    }

    memcpy( handler->recvPage + 1, data, handler->recvievedSize );
    handler->recvingState = RECV_STATE_CONTENT;

    PRINTF_2(("full header read; page size %d, position %d\n", handler->recvPage->u.s.size, handler->recvievedSize));

    return PRT_CONTINUE;
}

static Http_CallbackResult _ReadData(
    Http_SR_SocketData* handler)
{
    Http* self = (Http*)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;

    buf = ((char*)(handler->recvPage + 1)) + handler->recvievedSize;
    buf_size = handler->recvHeaders.contentLength - handler->recvievedSize;
    received = 0;

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

        PRINTF(("%d: read r = %d, recv = %d\n", (int)handler->base.sock, (int)r, (int)received ));

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

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

        handler->recvievedSize += received;
    }

    /* did we get all data? */
    PRINTF_2(("dt status - %d / %d\n", (int)handler->recvievedSize, (int)handler->recvHeaders.contentLength));

    if ( handler->recvievedSize != handler->recvHeaders.contentLength )
        return PRT_RETURN_TRUE;
 
    handler->requestIsBeingProcessed = MI_TRUE;
    (*self->callbackOnRequest)( self, self->callbackData, handler->connectionData, handler, &handler->recvHeaders,
        &handler->recvPage );

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

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

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

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

/* length of longest description - has to be updated if descriptions are updated */
#define HTTP_LONGEST_ERROR_DESCRIPTION 50
static const char* _GetHttpErrorCodeDescription(
    int httpErrorCode )
{
    switch (httpErrorCode)
    {
    case 200:
        return "OK";

    case 400:
        return "Bad Request";

    case 401:
        return "Unauthorized";

    case 500:
        return "Internal Server Error";
    }
    return "Error";
}

static Http_CallbackResult _WriteHeader(
    Http_SR_SocketData* handler)
{
#define RESPONSE_HEADER_FMT \
    "HTTP/1.1 %d %s\r\n"   \
    "Content-Length: %d\r\n"\
    "Connection: Keep-Alive\r\n"\
    "Content-Type: application/soap+xml;charset=UTF-8\r\n"\
    "\r\n"

/*    "SOAPAction: http://schemas.xmlsoap.org/ws/2004/08/addressing/fault\r\n"\ */

    char currentLine[sizeof(RESPONSE_HEADER_FMT) + 
        10 /* content length */ + 
        10 /*error code*/ + 
        HTTP_LONGEST_ERROR_DESCRIPTION /* code descirpiton */ ];
    char* buf;
    size_t buf_size, sent;
    MI_Result r;

    /* Do we have any data to send? */
    if (!handler->sendPage && 0 == handler->httpErrorCode)
        return PRT_RETURN_TRUE;

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

    if (handler->sendPage)
    {
        buf_size = (size_t)Snprintf(
            currentLine,
            sizeof(currentLine),
            RESPONSE_HEADER_FMT, 
            (int)handler->httpErrorCode, 
            _GetHttpErrorCodeDescription(handler->httpErrorCode),
            (int)handler->sendPage->u.s.size );
    }
    else
    {
        buf_size = (size_t)Snprintf(
            currentLine,
            sizeof(currentLine),
            "HTTP/1.1 %d %s\r\n\r\n", 
            (int)handler->httpErrorCode,
            _GetHttpErrorCodeDescription(handler->httpErrorCode));
    }

    buf = currentLine + handler->sentSize;

    sent = 0;

    r = _Sock_Write(handler, buf, buf_size - handler->sentSize, &sent);

    PRINTF(("%d: write r = %d, sent = %d; reverse %d\n", (int)handler->base.sock, (int)r, (int)sent, (int)handler->reverseOperations ));

    if ( r == MI_RESULT_OK && 0 == sent )
        return PRT_RETURN_FALSE; /* conection closed */

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

    if (!sent)
        return PRT_RETURN_TRUE;

    handler->sentSize += sent;

    if (handler->sentSize < buf_size)
        return PRT_RETURN_TRUE;

    handler->sentSize = 0;
    handler->sendingState = RECV_STATE_CONTENT;
    return PRT_CONTINUE;
}

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

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

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

    PRINTF(("%d: write r = %d, sent = %d\n", (int)handler->base.sock, (int)r, (int)sent ));

    if ( r == MI_RESULT_OK && 0 == sent )
        return PRT_RETURN_FALSE; /* conection closed */

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

    handler->sentSize += sent;

    /* did we get all data? */

    if ( handler->sentSize != handler->sendPage->u.s.size )
        return PRT_RETURN_TRUE;
 
    free(handler->sendPage);
    handler->sendPage = 0;
    handler->httpErrorCode = 0;
    handler->sentSize = 0;
    handler->sendingState = RECV_STATE_HEADER;
    handler->base.mask &= ~SELECTOR_WRITE;
    handler->base.mask |= SELECTOR_READ;

    return PRT_CONTINUE;
}

static MI_Boolean _RequestCallbackWrite(
    Http_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)
{
    Http_SR_SocketData* handler = (Http_SR_SocketData*)handlerIn;
    sel=sel;

    if ( ((mask & SELECTOR_READ) != 0 && !handler->reverseOperations) ||
        ((mask & SELECTOR_WRITE) != 0 && handler->reverseOperations) )
    {
        if (!_RequestCallbackRead(handler))
            return MI_FALSE;
    }

    if ( ((mask & SELECTOR_WRITE) != 0 && !handler->reverseOperations) ||
        ((mask & SELECTOR_READ) != 0 && handler->reverseOperations) )
    {
        if (!_RequestCallbackWrite(handler))
            return MI_FALSE;
    }
    
    /* re-set timeout - if we performed R/W operation, set timeout depending where we are in communication */
    if (mask & (SELECTOR_READ | SELECTOR_WRITE))
    {
        Http* self = (Http*)handler->base.data;

        if (handler->requestIsBeingProcessed)
        {
            /* since request is processed by server, disable timeout for this period */
            handler->base.fireTimeoutAt = TIME_NEVER;
        }
        else
        {
            /* Use configuration timeout */
            handler->base.fireTimeoutAt = currentTimeUsec + self->options.timeoutUsec;
        }
    }

    /* Close conenction by timeout */
    if (mask & SELECTOR_TIMEOUT)
        return MI_FALSE;

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

        /* notify next stack layer */
        (*self->callbackOnCloseConnection)(
            self, 
            self->callbackData, 
            handler->connectionData );

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

        PRINTF(("%d: close\n", (int)handler->base.sock));

        Sock_Close(handler->base.sock);

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

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

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

    return MI_TRUE;
}


static MI_Boolean _ListenerCallback(
    Selector* sel,
    Handler* handler_,
    MI_Uint32 mask, 
    MI_Uint64 currentTimeUsec)
{
    Http_Listener_SocketData* handler = (Http_Listener_SocketData*)handler_;
    Http* self = (Http*)handler->base.data;
    MI_Result r;
    Sock s;
    Addr addr;
    Http_SR_SocketData* h;

    sel=sel;
    mask=mask;
    currentTimeUsec = currentTimeUsec;

    if (mask & SELECTOR_READ)
    {
        int count;

        for (count = 0; count < 5; count++)
        {
            /* Accept the incoming connection */
            r = Sock_Accept(handler->base.sock, &s, &addr);

            PRINTF(("%d: accept r = %d\n", (int)s, (int)r ));

            if (MI_RESULT_WOULD_BLOCK == r)
                return MI_TRUE;


            if (r != MI_RESULT_OK)
            {
                LOGW((T("Sock_Accept() failed; err %d\n"), Sock_GetLastError()));
                return MI_TRUE;
            }

            r = Sock_SetBlocking(s, MI_FALSE);
            if (r != MI_RESULT_OK)
            {
                LOGW((T("Sock_SetBlocking() failed\n")));
                Sock_Close(s);
                return MI_TRUE;
            }

            /* Create handler */
            h = (Http_SR_SocketData*)calloc(1, sizeof(Http_SR_SocketData));

            if (!h)
            {
                Sock_Close(s);
                return MI_TRUE;
            }

            h->recvBufferSize = INITIAL_BUFFER_SIZE;
            h->recvBuffer = (char*)calloc(1, h->recvBufferSize);
            if (!h->recvBuffer)
            {
                free(h);
                Sock_Close(s);
                return MI_TRUE;
            }

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

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

                if (!h->ssl)
                {
                    LOGW((T("ssl_new() failed\n")));
                    free(h);
                    Sock_Close(s);
                    return MI_TRUE;
                }

                if (!(SSL_set_fd(h->ssl, s) ))
                {
                    LOGW((T("ssl_set_fd() failed\n")));
                    SSL_free(h->ssl);
                    free(h);
                    Sock_Close(s);
                    return MI_TRUE;
                }

            }

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

            if (r != MI_RESULT_OK)
            {
                LOGW((T("Selector_AddHandler() failed\n")));
                if (handler->secure)
                    SSL_free(h->ssl);
                free(h);
                Sock_Close(s);
                return MI_TRUE;
            }

            /* notify next stack layer about new connection */
            (*self->callbackOnNewConnection)(
                self, 
                self->callbackData, 
                h,
                &h->connectionData );
        }
    }

    if ((mask & SELECTOR_REMOVE) != 0 ||
        (mask & SELECTOR_DESTROY) != 0)
    {
        Sock_Close(handler->base.sock);
        free(handler);
    }

    return MI_TRUE;
}


static MI_Result _New_Http(
    Http** selfOut,
    Selector* selector, /*optional, maybe NULL*/
    HttpCallbackOnNewConnection callbackOnNewConnection,
    HttpCallbackOnCloseConnection callbackOnCloseConnection,
    HttpCallbackOnRequest callbackOnRequest,
    void* callbackData)
{
    Http* self;

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

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

    /* Allocate structure */
    {
        self = (Http*)calloc(1, sizeof(Http));

        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)
        {
            free(self);
            return MI_RESULT_FAILED;
        }
        self->selector = &self->internalSelector;
        self->internalSelectorUsed = MI_TRUE;
    }

    /* Save the callback and callbackData */
    self->callbackOnRequest = callbackOnRequest;
    self->callbackOnCloseConnection = callbackOnCloseConnection;
    self->callbackOnNewConnection = callbackOnNewConnection;
    self->callbackData = callbackData;

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

    /* options */
    {
        HttpOptions options = DEFAULT_HTTP_OPTIONS;

        self->options = options;
    }

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

#ifdef CONFIG_POSIX
static MI_Boolean _verifyPrivateKey(
    SSL_CTX *ctx, 
    const char* keyPath)
{
    // Open the private key file.

    FILE* is = fopen(keyPath, "r");

    if (!is)
    {
        LOGE_CHAR((
            "---> SSL: failed to open private key file: %s",
            keyPath));
        return MI_FALSE;
    }

    // Read the private key from the input stream.

    EVP_PKEY* pkey;
    pkey = PEM_read_PrivateKey(is, NULL, NULL, NULL);

    if (!pkey)
    {
        LOGE_CHAR(("---> SSL: failed to create private key"));
        return MI_FALSE;
    }

    /* Close the input stream. */

    fclose(is);

    /* Associate the new private key with the SSL context object. */

    if (SSL_CTX_use_PrivateKey(ctx, pkey) <= 0)
    {
        EVP_PKEY_free(pkey);
        LOGE_CHAR((
            "---> SSL: no private key found in %s",
            keyPath));
        return MI_FALSE;
    }

    EVP_PKEY_free(pkey);

    /* Check private key for validity. */

    if (!SSL_CTX_check_private_key(ctx))
    {
        LOGE_CHAR((
            "---> SSL: Private and public key do not match"));
        return MI_FALSE;
    }

    return MI_TRUE;
}

static MI_Result _CreateSSLContext(Http* self)
{
    SSL_CTX * sslContext = 0;

    sslContext = SSL_CTX_new(SSLv23_method());

    if (!sslContext)
    {
        LOGE_CHAR((
            "---> SSL: cannot create ssl context"));
        return MI_RESULT_FAILED;
    }
    SSL_CTX_set_quiet_shutdown(sslContext, 1);
    SSL_CTX_set_mode(sslContext, SSL_MODE_AUTO_RETRY);
    SSL_CTX_set_mode(sslContext, SSL_MODE_ENABLE_PARTIAL_WRITE);
    SSL_CTX_set_session_cache_mode(sslContext, SSL_SESS_CACHE_OFF);

    /* Check if there is a certificate file (file containing server
    ** certificate) specified. If specified, validate and load the
    ** certificate.
    */
    {
        /* load the specified server certificates */
        LOGI_CHAR(("---> SSL: Loading server certificate from: %s", 
            GetPath(ID_PEMFILE)));

        if (SSL_CTX_use_certificate_file(sslContext,
            GetPath(ID_PEMFILE), SSL_FILETYPE_PEM) <=0)
        {
            LOGE_CHAR(("---> SSL: No server certificate found in %s", 
                GetPath(ID_PEMFILE)));
            SSL_CTX_free(sslContext);
            return MI_RESULT_FAILED;
        }
    }

    /*
    ** Check if there is a key file (file containing server
    ** private key) specified and the key was not already loaded.
    ** If specified, validate and load the key.
    */
    {
        /* load the specified server certificates */
        LOGI_CHAR(("---> SSL: Loading certificate's private key from: %s",
            GetPath(ID_KEYFILE)));

        //
        // load given private key and check for validity
        //
        if (!_verifyPrivateKey(sslContext, GetPath(ID_KEYFILE)))
        {
            LOGE_CHAR((
                "---> SSL: No server certificate found in %s",
                GetPath(ID_KEYFILE)));
            SSL_CTX_free(sslContext);
            return MI_RESULT_FAILED;
        }
    }

    self->sslContext = sslContext;
    return MI_RESULT_OK;
}
#endif

static MI_Result _CreateAddListenerSocket(
    Http* self,
    unsigned short port,
    MI_Boolean  secure
    )
{
    Addr addr;
    Sock listener;
    MI_Result r;

    /* Create listener socket */
    {
        Addr_InitAny(&addr, port);
        r = Sock_CreateListener(&listener, &addr);

        if (r != MI_RESULT_OK)
        {
            return r;
        }

        r = Sock_SetBlocking(listener, MI_FALSE);

        if (r != MI_RESULT_OK)
        {
            Sock_Close(listener);
            return r;
        }
    }

    /* Watch for read events on the listener socket (client connections) */
    {
        Http_Listener_SocketData* h = (Http_Listener_SocketData*)calloc(1, sizeof(Http_Listener_SocketData));

        if (!h)
        {
            Sock_Close(listener);
            return MI_RESULT_FAILED;
        }

        h->base.sock = listener;
        h->base.mask = SELECTOR_READ | SELECTOR_EXCEPTION;
        h->base.callback = _ListenerCallback;
        h->base.data = self;
        h->secure = secure;

        r = Selector_AddHandler(self->selector, &h->base);

        if (r != MI_RESULT_OK)
        {
            Sock_Close(listener);
            free(h);
            return r;
        }
    }

    return MI_RESULT_OK;
}

MI_Result Http_New_Server(
    Http** selfOut,
    Selector* selector, /*optional, maybe NULL*/
    unsigned short http_port,   /* 0 to disable */
    unsigned short https_port,  /* 0 to disable */
    HttpCallbackOnNewConnection callbackOnNewConnection,
    HttpCallbackOnCloseConnection callbackOnCloseConnection,
    HttpCallbackOnRequest callbackOnRequest,
    void* callbackData)
{
    Http* self;
    MI_Result r;

    /* allocate this, inits selector */
    r = _New_Http(selfOut, selector, callbackOnNewConnection, 
        callbackOnCloseConnection, callbackOnRequest, callbackData);

    if (MI_RESULT_OK != r)
        return r;

    self = *selfOut;

    /* Create http listener socket */
    if (http_port)
    {
        r = _CreateAddListenerSocket(self, http_port, MI_FALSE);

        if (r != MI_RESULT_OK)
        {
            Http_Delete(self);
            return r;
        }
    }

#ifdef CONFIG_POSIX
    /* Create https listener socket */
    if (https_port)
    {
        /* init ssl */
        SSL_library_init();

        /* create context */
        r = _CreateSSLContext(self);

        if (r != MI_RESULT_OK)
        {
            Http_Delete(self);
            return r;
        }

        /* create a socket */
        r = _CreateAddListenerSocket(self, https_port, MI_TRUE);

        if (r != MI_RESULT_OK)
        {
            Http_Delete(self);
            return r;
        }
    }
#else
    MI_UNUSED(https_port);
#endif

    return MI_RESULT_OK;
}

MI_Result Http_Delete(
    Http* self)
{
    /* Check parameters */
    if (!self)
        return MI_RESULT_INVALID_PARAMETER;

    /* Check magic number */
    if (self->magic != _MAGIC)
        return MI_RESULT_INVALID_PARAMETER;

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

        /* Shutdown the network */
        Sock_Stop();
    }

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

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

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

    return MI_RESULT_OK;
}

MI_Result Http_Run(
    Http* self,
    MI_Uint64 timeoutUsec)
{
    /* Run the selector */
    return Selector_Run(self->selector, timeoutUsec);
}

/* sends 'ok' response with provided content;
 if message is accepted to be sent, on return *data == null (taking memory ownership)*/
MI_Result Http_SendResponse(
    Http* self,
    void* httpConnectionHanlde,
    int httpErrorCode,
    Page** data)
{
    Http_SR_SocketData* sendSock;

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

    if (self->magic != _MAGIC)
    {
        LOGW((T("_SendIN_IO_thread: invalid magic!") ));
        return MI_RESULT_INVALID_PARAMETER;
    }

    sendSock = (Http_SR_SocketData*)httpConnectionHanlde;

    /* validate handler */

    if (MI_RESULT_OK != Selector_ContainsHandler(
            self->selector, (Handler*)sendSock ) )
    {
        LOGW((T("cannot send message: invalid handler (msg->clientID) %p\n"), sendSock));
        return MI_RESULT_INVALID_PARAMETER;
    }

    sendSock->requestIsBeingProcessed = MI_FALSE;

    sendSock->base.mask |= SELECTOR_WRITE;
    sendSock->base.mask &= ~SELECTOR_READ;

    if (data)
    {
        sendSock->sendPage = *data;
        *data = 0;
    }
    else
    {
        sendSock->sendPage = 0;
    }
    sendSock->httpErrorCode = httpErrorCode;

    sendSock->sentSize = 0;
    sendSock->sendingState = RECV_STATE_HEADER;

    _RequestCallbackWrite(sendSock);

    return MI_RESULT_OK;
}

/* Sets http options (mostly unit-test support) */
MI_Result Http_SetOptions(
    Http* self,
    const HttpOptions* options)
{
    /* check params */
    if (!self || !options)
        return MI_RESULT_INVALID_PARAMETER;

    if (self->magic != _MAGIC)
    {
        LOGW((T("Http_SetOptions: invalid magic!") ));
        return MI_RESULT_INVALID_PARAMETER;
    }

    self->options = *options;
    return MI_RESULT_OK;
}

ViewCVS 0.9.2