/* **============================================================================== ** ** 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 #include //#include "http.h" #include "httpclient.h" #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_POSIX #include #include #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 #define T MI_T /* #define ENABLE_TRACING // */ #ifdef ENABLE_TRACING #define PRINTF(a) TIMESTAMP(); printf a #define TIMESTAMP() \ {\ MI_Uint64 currentTimeUsec = 0;\ \ Time_Now(¤tTimeUsec);\ 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 = 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 _Http_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 */ char* recvBuffer; size_t recvBufferSize; size_t recvievedSize; Http_RecvState recvingState; HttpClientHeaderField recvHeaderFields[64]; HttpClientResponseHeader recvHeaders; MI_Sint64 contentLength; Page* recvPage; /* sending part */ Page* sendPage; Page* sendHeader; size_t sentSize; Http_RecvState sendingState; /* general operation status */ MI_Result status; } Http_SR_SocketData; struct _HttpClient { MI_Uint32 magic; Selector internalSelector; Selector* selector; HttpClientCallbackOnStatus callbackOnStatus; HttpClientCallbackOnResponse callbackOnResponse; void* callbackData; SSL_CTX* sslContext; Http_SR_SocketData* connector; MI_Boolean internalSelectorUsed; }; /* 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 _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 MI_Boolean _getHeaderField( Http_SR_SocketData* handler, char ** 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 ) return MI_FALSE;*/ } else if (nameHashCode == _HashCode('t','g',17) && /*Transfer-Encoding*/ Strcasecmp(name,"Transfer-Encoding") == 0) { handler->contentLength = -1; } else { 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 { LOGW_CHAR(("too many http headers; skipping %s: %s\n", name, 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( 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 */ /* 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 + 9; /*+ HTTP/1.1*/ handler->recvHeaders.httpError = Strtoul(s, NULL, 10); } /* 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; res = SSL_read(handler->ssl, buf, buf_size); PRINTF(("ssl read %d\n", res)); 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_READ; handler->base.mask |= SELECTOR_WRITE; 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; if (handler->connectDone) { res = SSL_write(handler->ssl, buf, buf_size); PRINTF(("ssl write %d\n", res)); } else { res = SSL_connect(handler->ssl); PRINTF(("ssl connect %d\n", res)); if ( res > 0 ) { /* we are done with accpet */ handler->connectDone = MI_TRUE; return _Sock_Write(handler,buf,buf_size,sizeWritten); } /* perform regular error checking */ } 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: PRINTF(("ssl write/connetc WANT_WRITE\n")); return MI_RESULT_WOULD_BLOCK; case SSL_ERROR_WANT_READ: PRINTF(("ssl write/connetc WANT_READ\n")); 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_HEADER) 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, ¤tLine)) return PRT_RETURN_FALSE; while ((data-currentLine) > 3) { if (!_getHeaderField(handler, ¤tLine)) return PRT_RETURN_FALSE; } /* Check if we have to deal with chunked-encoded data */ if (handler->contentLength < 0) { handler->recvievedSize -= 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)) return PRT_RETURN_FALSE; } /* remove consumed header part */ memmove(handler->recvBuffer, data, handler->recvievedSize); handler->recvingState = RECV_STATE_CHUNKHEADER; return PRT_CONTINUE; } /* Allocate zero-terminated buffer */ handler->recvPage = (Page*)malloc(sizeof(Page) + (size_t)handler->contentLength + 1); if (!handler->recvPage) return PRT_RETURN_FALSE; ((char*)(handler->recvPage + 1))[handler->contentLength] = 0; handler->recvPage->u.s.size = (unsigned int)handler->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 have more, assuming http client is invalid and drop connection */ if (handler->recvievedSize > (size_t)handler->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)); /* 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)) return PRT_RETURN_FALSE; } return PRT_CONTINUE; } static Http_CallbackResult _ReadData( Http_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; buf = ((char*)(handler->recvPage + 1)) + handler->recvievedSize; buf_size = (size_t)(handler->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->contentLength)); if ( handler->recvievedSize != (size_t)handler->contentLength ) 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_TRUE, &handler->recvPage)) return PRT_RETURN_FALSE; /* status callback */ handler->status = MI_RESULT_OK; (*self->callbackOnStatus)( self, self->callbackData, MI_RESULT_OK ); } 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 Http_CallbackResult _ReadChunkHeader( Http_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->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 ) { if (!handler->recvBufferSize) return PRT_RETURN_FALSE; /* conection 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->recvievedSize += received; /* check header */ PRINTF_2(("%s\n",buf)); /* did we get full header? */ buf = handler->recvBuffer; for ( index = 1; index < handler->recvievedSize; index++ ) { if (buf[index-1] == '\r' && buf[index] == '\n' ) { fullHeaderReceived = MI_TRUE; break; } } if (!fullHeaderReceived ) { if (connectionClosed) return PRT_RETURN_FALSE; /* conection closed */ 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 _ReadChunkHeader(handler); } else { /* http chunk header is too big - drop connection */ LOGW((T("http chunk 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 (!_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->recvievedSize = 0; memset(&handler->recvHeaders, 0, sizeof(handler->recvHeaders)); handler->recvingState = RECV_STATE_HEADER; if (connectionClosed) return PRT_RETURN_FALSE; /* conection closed */ return PRT_CONTINUE; } /* Allocate zero-terminated buffer */ handler->recvPage = (Page*)malloc(sizeof(Page) + (size_t)chunkSize + 2 /*CR-LF*/ + 1 /* \0 */); 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->recvievedSize -= index + 1; /* in case of small chunks we may receive more than one chunk already */ if (handler->recvievedSize > (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) free(handler->recvPage); handler->recvPage = 0; } /* remove consumed part */ memmove(handler->recvBuffer, data + chunkSize+2, handler->recvievedSize - (chunkSize+2)); handler->recvievedSize -= (chunkSize+2); /* consume next chunk */ return _ReadChunkHeader(handler); } memcpy( handler->recvPage + 1, data, handler->recvievedSize ); handler->recvingState = RECV_STATE_CHUNKDATA; if (connectionClosed) return PRT_RETURN_FALSE; /* conection closed */ return PRT_CONTINUE; } static Http_CallbackResult _ReadChunkData( Http_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->recvievedSize; buf_size = (size_t)(handler->recvPage->u.s.size + 2 /* CR-LF */ - 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->contentLength)); if ( handler->recvievedSize != (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) free(handler->recvPage); handler->recvPage = 0; handler->recvievedSize = 0; memset(&handler->recvHeaders, 0, sizeof(handler->recvHeaders)); handler->recvingState = RECV_STATE_CHUNKHEADER; return PRT_CONTINUE; } static Http_CallbackResult _WriteHeader( Http_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; 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); 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->sendHeader->u.s.size ) return PRT_RETURN_TRUE; free(handler->sendHeader); handler->sendHeader = 0; 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->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->sentSize = 0; handler->sendingState = RECV_STATE_HEADER; handler->base.mask &= ~SELECTOR_WRITE; handler->base.mask |= SELECTOR_READ; 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; } 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( 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)) { handler->base.fireTimeoutAt = currentTimeUsec + handler->timeoutUsec; } /* Close conenction by timeout */ if (mask & SELECTOR_TIMEOUT) { if (MI_RESULT_OK != handler->status) handler->status = MI_RESULT_TIME_OUT; return MI_FALSE; } if ((mask & SELECTOR_REMOVE) != 0 || (mask & SELECTOR_DESTROY) != 0) { HttpClient* self = (HttpClient*)handler->base.data; /* notify next stack layer */ if (MI_RESULT_OK != handler->status) (*self->callbackOnStatus)( self, self->callbackData, handler->status); self->connector = 0; 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); if (handler->sendHeader) free(handler->sendHeader); free(handler->recvBuffer); free(handler); } return MI_TRUE; } #ifdef CONFIG_POSIX static MI_Result _CreateSSLContext(HttpClient* 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); #if 0 /* 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; } } #endif self->sslContext = sslContext; return MI_RESULT_OK; } #endif static MI_Result _CreateConnectorSocket( HttpClient* self, const char* host, unsigned short port, MI_Boolean secure) { Addr addr; MI_Result r; Sock s; Http_SR_SocketData* h; MI_Uint64 currentTimeUsec; /* timeout calculation */ if (MI_RESULT_OK != Time_Now(¤tTimeUsec)) return MI_RESULT_FAILED; // Initialize address. r = Addr_Init(&addr, host, port); if (r != MI_RESULT_OK) return MI_RESULT_FAILED; // Create client socket. r = Sock_Create(&s); if (r != MI_RESULT_OK) { Sock_Close(s); return MI_RESULT_FAILED; } r = Sock_SetBlocking(s, MI_FALSE); if (r != MI_RESULT_OK) { Sock_Close(s); return MI_RESULT_FAILED; } // Connect to the server. r = Sock_Connect(s, &addr); if (r != MI_RESULT_OK && r != MI_RESULT_WOULD_BLOCK) { Sock_Close(s); return MI_RESULT_FAILED; } /* Create handler */ h = (Http_SR_SocketData*)calloc(1, sizeof(Http_SR_SocketData)); if (!h) { Sock_Close(s); return MI_RESULT_FAILED; } h->recvBufferSize = INITIAL_BUFFER_SIZE; h->recvBuffer = (char*)calloc(1, h->recvBufferSize); if (!h->recvBuffer) { free(h); Sock_Close(s); 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) { LOGW((T("ssl_new() failed\n"))); free(h); Sock_Close(s); return MI_RESULT_FAILED; } 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_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) { LOGW((T("Selector_AddHandler() failed\n"))); if (secure) SSL_free(h->ssl); free(h); Sock_Close(s); return MI_RESULT_FAILED; } self->connector = h; 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*)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) { free(self); 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 */ pageSize += Strlen(verb); pageSize += Strlen(uri); if (headers) pageSize += _GetHeadersSize(headers); page = (Page*)malloc(pageSize + sizeof(Page)); 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) { 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) { HttpClient* self; MI_Result r; /* allocate this, inits selector */ r = _New_Http(selfOut, selector, statusCallback, responseCallback, callbackData); if (MI_RESULT_OK != r) return r; self = *selfOut; #ifdef CONFIG_POSIX /* Allocate ssl context */ if (secure) { /* init ssl */ SSL_library_init(); /* create context */ r = _CreateSSLContext(self); if (r != MI_RESULT_OK) { HttpClient_Delete(self); 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); 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) 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(); } 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 */ 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) { /* check params */ if (!self || !uri) return MI_RESULT_INVALID_PARAMETER; if (self->magic != _MAGIC) { LOGW((T("start-request: invalid magic!") )); return MI_RESULT_INVALID_PARAMETER; } if (!self->connector) { LOGW((T("start-request: connection was closed") )); return MI_RESULT_FAILED; } /* create header page */ self->connector->sendHeader = _CreateHttpHeader(verb, uri, headers, (data && *data) ? (*data)->u.s.size : 0); if (data) { self->connector->sendPage = *data; *data = 0; } else { self->connector->sendPage = 0; } /* 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_Result HttpClient_SetTimeout( HttpClient* self, MI_Uint64 timeoutUsec) { MI_Uint64 currentTimeUsec = 0; Time_Now(¤tTimeUsec); /* check params */ if (!self) return MI_RESULT_INVALID_PARAMETER; if (self->magic != _MAGIC) { LOGW((T("setTimeout: invalid magic!") )); 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; }