Azure IoT common library
Fork of azure_c_shared_utility by
Diff: httpapi_compact.c
- Revision:
- 6:c55b013dfc2a
- Child:
- 7:1af47e3a19b6
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/httpapi_compact.c Fri Jul 01 10:43:23 2016 -0700 @@ -0,0 +1,800 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#include <stdlib.h> +#ifdef _CRTDBG_MAP_ALLOC +#include <crtdbg.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> +#include "azure_c_shared_utility/httpapi.h" +#include "azure_c_shared_utility/httpheaders.h" +#include "azure_c_shared_utility/crt_abstractions.h" +#include "azure_c_shared_utility/xlogging.h" +#include "azure_c_shared_utility/xio.h" +#include "azure_c_shared_utility/platform.h" +#include "azure_c_shared_utility/tlsio.h" +#include "azure_c_shared_utility/threadapi.h" +#include <string.h> + +#define MAX_HOSTNAME 64 +#define TEMP_BUFFER_SIZE 4096 + +#define CHAR_COUNT(A) (sizeof(A) - 1) + +DEFINE_ENUM_STRINGS(HTTPAPI_RESULT, HTTPAPI_RESULT_VALUES) + +typedef enum SEND_ALL_RESULT_TAG +{ + SEND_ALL_RESULT_NOT_STARTED, + SEND_ALL_RESULT_PENDING, + SEND_ALL_RESULT_OK, + SEND_ALL_RESULT_ERROR +} SEND_ALL_RESULT; + +typedef struct HTTP_HANDLE_DATA_TAG +{ + char host[MAX_HOSTNAME]; + char* certificate; + XIO_HANDLE xio_handle; + size_t received_bytes_count; + unsigned char* received_bytes; + SEND_ALL_RESULT send_all_result; + unsigned int is_io_error : 1; + unsigned int is_connected : 1; +} HTTP_HANDLE_DATA; + +HTTPAPI_RESULT HTTPAPI_Init(void) +{ + return HTTPAPI_OK; +} + +void HTTPAPI_Deinit(void) +{ +} + +HTTP_HANDLE HTTPAPI_CreateConnection(const char* hostName) +{ + HTTP_HANDLE_DATA* handle = NULL; + + if (hostName) + { + handle = (HTTP_HANDLE_DATA*)malloc(sizeof(HTTP_HANDLE_DATA)); + if (handle != NULL) + { + if (strcpy_s(handle->host, MAX_HOSTNAME, hostName) != 0) + { + LogError("HTTPAPI_CreateConnection::Could not strcpy_s"); + free(handle); + handle = NULL; + } + else + { + TLSIO_CONFIG tlsio_config = { hostName, 443 }; + handle->xio_handle = xio_create(platform_get_default_tlsio(), (void*)&tlsio_config); + if (handle->xio_handle == NULL) + { + LogError("HTTPAPI_CreateConnection::xio_create failed"); + free(handle->host); + free(handle); + handle = NULL; + } + else + { + handle->is_connected = 0; + handle->is_io_error = 0; + handle->received_bytes_count = 0; + handle->received_bytes = NULL; + handle->send_all_result = SEND_ALL_RESULT_NOT_STARTED; + handle->certificate = NULL; + } + } + } + } + else + { + LogInfo("HTTPAPI_CreateConnection:: null hostName parameter"); + } + + return (HTTP_HANDLE)handle; +} + +void HTTPAPI_CloseConnection(HTTP_HANDLE handle) +{ + HTTP_HANDLE_DATA* h = (HTTP_HANDLE_DATA*)handle; + + if (h) + { + if (h->xio_handle != NULL) + { + LogInfo("HTTPAPI_CloseConnection xio_destroy(); to %s", h->host); + xio_destroy(h->xio_handle); + } + + if (h->certificate) + { + free(h->certificate); + } + + free(h); + } +} + +static void on_io_open_complete(void* context, IO_OPEN_RESULT open_result) +{ + HTTP_HANDLE_DATA* h = (HTTP_HANDLE_DATA*)context; + if (open_result == IO_OPEN_OK) + { + h->is_connected = 1; + h->is_io_error = 0; + } + else + { + h->is_io_error = 1; + } +} + +static int my_strnicmp(const char* s1, const char* s2, size_t n) +{ + size_t i; + int result = 0; + + for (i = 0; i < n; i++) + { + /* compute the difference between the chars */ + result = tolower(s1[i]) - tolower(s2[i]); + + /* break if we have a difference ... */ + if ((result != 0) || + /* ... or if we got to the end of one the strings */ + (s1[i] == '\0') || (s2[i] == '\0')) + { + break; + } + } + + return result; +} + +static int my_stricmp(const char* s1, const char* s2) +{ + size_t i = 0; + + while ((s1[i] != '\0') && (s2[i] != '\0')) + { + /* break if we have a difference ... */ + if (tolower(s1[i]) != tolower(s2[i])) + { + break; + } + + i++; + } + + /* if we broke because we are at end of string this will yield 0 */ + /* if we broke because there was a difference this will yield non-zero */ + return tolower(s1[i]) - tolower(s2[i]); +} + +static void on_bytes_received(void* context, const unsigned char* buffer, size_t size) +{ + HTTP_HANDLE_DATA* h = (HTTP_HANDLE_DATA*)context; + + /* Here we got some bytes so we'll buffer them so the receive functions can consumer it */ + unsigned char* new_received_bytes = (unsigned char*)realloc(h->received_bytes, h->received_bytes_count + size); + if (new_received_bytes == NULL) + { + h->is_io_error = 1; + LogError("on_bytes_received: Error allocating memory for received data"); + } + else + { + h->received_bytes = new_received_bytes; + (void)memcpy(h->received_bytes + h->received_bytes_count, buffer, size); + h->received_bytes_count += size; + } +} + +static void on_io_error(void* context) +{ + HTTP_HANDLE_DATA* h = (HTTP_HANDLE_DATA*)context; + h->is_io_error = 1; + LogError("on_io_error: Error signalled by underlying IO"); +} + +static int conn_receive(HTTP_HANDLE_DATA* http_instance, char* buffer, int count) +{ + int result = 0; + + if (count < 0) + { + result = -1; + } + else + { + while (result < count) + { + xio_dowork(http_instance->xio_handle); + + /* if any error was detected while receiving then simply break and report it */ + if (http_instance->is_io_error != 0) + { + result = -1; + break; + } + + if (http_instance->received_bytes_count >= (size_t)count) + { + /* Consuming bytes from the receive buffer */ + (void)memcpy(buffer, http_instance->received_bytes, count); + (void)memmove(http_instance->received_bytes, http_instance->received_bytes + count, http_instance->received_bytes_count - count); + http_instance->received_bytes_count -= count; + + /* we're not reallocating at each consumption so that we don't trash due to byte by byte consumption */ + if (http_instance->received_bytes_count == 0) + { + free(http_instance->received_bytes); + http_instance->received_bytes = NULL; + } + + result = count; + break; + } + + ThreadAPI_Sleep(1); + } + } + + return result; +} + +static void on_send_complete(void* context, IO_SEND_RESULT send_result) +{ + /* If a send is complete we'll simply signal this by changing the send all state */ + HTTP_HANDLE_DATA* http_instance = (HTTP_HANDLE_DATA*)context; + if (send_result == IO_SEND_OK) + { + http_instance->send_all_result = SEND_ALL_RESULT_OK; + } + else + { + http_instance->send_all_result = SEND_ALL_RESULT_ERROR; + } +} + +static int conn_send_all(HTTP_HANDLE_DATA* http_instance, char* buffer, int count) +{ + int result; + + if (count < 0) + { + result = -1; + } + else + { + http_instance->send_all_result = SEND_ALL_RESULT_PENDING; + if (xio_send(http_instance->xio_handle, buffer, count, on_send_complete, http_instance) != 0) + { + result = -1; + } + else + { + /* We have to loop in here until all bytes are sent or we encounter an error. */ + while (1) + { + xio_dowork(http_instance->xio_handle); + + /* If we got an error signalled from the underlying IO we simply report it up */ + if (http_instance->is_io_error) + { + http_instance->send_all_result = SEND_ALL_RESULT_ERROR; + break; + } + + if (http_instance->send_all_result != SEND_ALL_RESULT_PENDING) + { + break; + } + + /* We yield the CPU for a bit so others can do their work */ + ThreadAPI_Sleep(1); + } + + /* The send_all_result indicates what is the status for the send operation. + Not started - means nothing should happen since no send was started + Pending - a send was started, but it is still being carried out + Ok - Send complete + Error - error */ + switch (http_instance->send_all_result) + { + default: + case SEND_ALL_RESULT_NOT_STARTED: + result = -1; + break; + + case SEND_ALL_RESULT_OK: + result = count; + break; + + case SEND_ALL_RESULT_ERROR: + result = -1; + break; + } + } + } + + return result; +} + +static int readLine(HTTP_HANDLE_DATA* http_instance, char* buf, const size_t size) +{ + // reads until \r\n is encountered. writes in buf all the characters + char* p = buf; + char c; + if (conn_receive(http_instance, &c, 1) < 0) + return -1; + while (c != '\r') { + if ((p - buf + 1) >= (int)size) + return -1; + *p++ = c; + if (conn_receive(http_instance, &c, 1) < 0) + return -1; + } + *p = 0; + if (conn_receive(http_instance, &c, 1) < 0 || c != '\n') // skip \n + return -1; + return p - buf; +} + +static int readChunk(HTTP_HANDLE_DATA* http_instance, char* buf, size_t size) +{ + size_t cur, offset; + + // read content with specified length, even if it is received + // only in chunks due to fragmentation in the networking layer. + // returns -1 in case of error. + offset = 0; + while (size > 0) + { + cur = conn_receive(http_instance, buf + offset, size); + + // end of stream reached + if (cur == 0) + return offset; + + // read cur bytes (might be less than requested) + size -= cur; + offset += cur; + } + + return offset; +} + +static int skipN(HTTP_HANDLE_DATA* http_instance, size_t n, char* buf, size_t size) +{ + size_t org = n; + // read and abandon response content with specified length + // returns -1 in case of error. + while (n > size) + { + if (readChunk(http_instance, (char*)buf, size) < 0) + return -1; + + n -= size; + } + + if (readChunk(http_instance, (char*)buf, n) < 0) + return -1; + + return org; +} + +//Note: This function assumes that "Host:" and "Content-Length:" headers are setup +// by the caller of HTTPAPI_ExecuteRequest() (which is true for httptransport.c). +HTTPAPI_RESULT HTTPAPI_ExecuteRequest(HTTP_HANDLE handle, HTTPAPI_REQUEST_TYPE requestType, const char* relativePath, + HTTP_HEADERS_HANDLE httpHeadersHandle, const unsigned char* content, + size_t contentLength, unsigned int* statusCode, + HTTP_HEADERS_HANDLE responseHeadersHandle, BUFFER_HANDLE responseContent) +{ + + HTTPAPI_RESULT result; + size_t headersCount; + char buf[TEMP_BUFFER_SIZE]; + int ret; + size_t bodyLength = 0; + bool chunked = false; + const unsigned char* receivedContent; + + const char* method = (requestType == HTTPAPI_REQUEST_GET) ? "GET" + : (requestType == HTTPAPI_REQUEST_POST) ? "POST" + : (requestType == HTTPAPI_REQUEST_PUT) ? "PUT" + : (requestType == HTTPAPI_REQUEST_DELETE) ? "DELETE" + : (requestType == HTTPAPI_REQUEST_PATCH) ? "PATCH" + : NULL; + + if (handle == NULL || + relativePath == NULL || + httpHeadersHandle == NULL || + method == NULL || + HTTPHeaders_GetHeaderCount(httpHeadersHandle, &headersCount) != HTTP_HEADERS_OK) + { + result = HTTPAPI_INVALID_ARG; + LogError("(result = %s)", ENUM_TO_STRING(HTTPAPI_RESULT, result)); + goto exit; + } + + HTTP_HANDLE_DATA* httpHandle = (HTTP_HANDLE_DATA*)handle; + + if (handle->is_connected == 0) + { + // Load the certificate + if ((httpHandle->certificate != NULL) && + (xio_setoption(httpHandle->xio_handle, "TrustedCerts", httpHandle->certificate) != 0)) + { + result = HTTPAPI_ERROR; + LogError("Could not load certificate (result = %s)", ENUM_TO_STRING(HTTPAPI_RESULT, result)); + goto exit; + } + + // Make the connection + if (xio_open(httpHandle->xio_handle, on_io_open_complete, httpHandle, on_bytes_received, httpHandle, on_io_error, httpHandle) != 0) + { + result = HTTPAPI_ERROR; + LogError("Could not connect (result = %s)", ENUM_TO_STRING(HTTPAPI_RESULT, result)); + goto exit; + } + + while (1) + { + xio_dowork(httpHandle->xio_handle); + if ((handle->is_connected == 1) || + (handle->is_io_error == 1)) + { + break; + } + + ThreadAPI_Sleep(1); + } + } + + //Send request + if ((ret = snprintf(buf, sizeof(buf), "%s %s HTTP/1.1\r\n", method, relativePath)) < 0 + || ret >= sizeof(buf)) + { + result = HTTPAPI_STRING_PROCESSING_ERROR; + LogError("(result = %s)", ENUM_TO_STRING(HTTPAPI_RESULT, result)); + goto exit; + } + if (conn_send_all(httpHandle, buf, strlen(buf)) < 0) + { + result = HTTPAPI_SEND_REQUEST_FAILED; + LogError("(result = %s)", ENUM_TO_STRING(HTTPAPI_RESULT, result)); + goto exit; + } + + //Send default headers + for (size_t i = 0; i < headersCount; i++) + { + char* header; + if (HTTPHeaders_GetHeader(httpHeadersHandle, i, &header) != HTTP_HEADERS_OK) + { + result = HTTPAPI_HTTP_HEADERS_FAILED; + LogError("(result = %s)", ENUM_TO_STRING(HTTPAPI_RESULT, result)); + goto exit; + } + if (conn_send_all(httpHandle, header, strlen(header)) < 0) + { + result = HTTPAPI_SEND_REQUEST_FAILED; + LogError("(result = %s)", ENUM_TO_STRING(HTTPAPI_RESULT, result)); + free(header); + goto exit; + } + if (conn_send_all(httpHandle, "\r\n", 2) < 0) + { + result = HTTPAPI_SEND_REQUEST_FAILED; + LogError("(result = %s)", ENUM_TO_STRING(HTTPAPI_RESULT, result)); + free(header); + goto exit; + } + free(header); + } + + //Close headers + if (conn_send_all(httpHandle, "\r\n", 2) < 0) + { + result = HTTPAPI_SEND_REQUEST_FAILED; + LogError("(result = %s)", ENUM_TO_STRING(HTTPAPI_RESULT, result)); + goto exit; + } + + //Send data (if available) + if (content && contentLength > 0) + { + if (conn_send_all(httpHandle, (char*)content, contentLength) < 0) + { + result = HTTPAPI_SEND_REQUEST_FAILED; + LogError("(result = %s)", ENUM_TO_STRING(HTTPAPI_RESULT, result)); + goto exit; + } + } + + //Receive response + if (readLine(httpHandle, buf, sizeof(buf)) < 0) + { + result = HTTPAPI_READ_DATA_FAILED; + LogError("(result = %s)", ENUM_TO_STRING(HTTPAPI_RESULT, result)); + goto exit; + } + + //Parse HTTP response + if (sscanf(buf, "HTTP/%*d.%*d %d %*[^\r\n]", &ret) != 1) + { + //Cannot match string, error + LogInfo("HTTPAPI_ExecuteRequest::Not a correct HTTP answer=%s", buf); + result = HTTPAPI_READ_DATA_FAILED; + LogError("(result = %s)", ENUM_TO_STRING(HTTPAPI_RESULT, result)); + goto exit; + } + if (statusCode) + *statusCode = ret; + + //Read HTTP response headers + if (readLine(httpHandle, buf, sizeof(buf)) < 0) + { + result = HTTPAPI_READ_DATA_FAILED; + LogError("(result = %s)", ENUM_TO_STRING(HTTPAPI_RESULT, result)); + goto exit; + } + + while (buf[0]) + { + const char ContentLength[] = "content-length:"; + const char TransferEncoding[] = "transfer-encoding:"; + + if (my_strnicmp(buf, ContentLength, CHAR_COUNT(ContentLength)) == 0) + { + if (sscanf(buf + CHAR_COUNT(ContentLength), " %d", &bodyLength) != 1) + { + result = HTTPAPI_READ_DATA_FAILED; + LogError("(result = %s)", ENUM_TO_STRING(HTTPAPI_RESULT, result)); + goto exit; + } + } + else if (my_strnicmp(buf, TransferEncoding, CHAR_COUNT(TransferEncoding)) == 0) + { + const char* p = buf + CHAR_COUNT(TransferEncoding); + while (isspace(*p)) p++; + if (my_stricmp(p, "chunked") == 0) + chunked = true; + } + + char* whereIsColon = strchr((char*)buf, ':'); + if (whereIsColon && responseHeadersHandle != NULL) + { + *whereIsColon = '\0'; + HTTPHeaders_AddHeaderNameValuePair(responseHeadersHandle, buf, whereIsColon + 1); + } + + if (readLine(httpHandle, buf, sizeof(buf)) < 0) + { + result = HTTPAPI_READ_DATA_FAILED; + LogError("(result = %s)", ENUM_TO_STRING(HTTPAPI_RESULT, result)); + goto exit; + } + } + + //Read HTTP response body + if (!chunked) + { + if (bodyLength) + { + if (responseContent != NULL) + { + if (BUFFER_pre_build(responseContent, bodyLength) != 0) + { + result = HTTPAPI_ALLOC_FAILED; + LogError("(result = %s)", ENUM_TO_STRING(HTTPAPI_RESULT, result)); + } + else if (BUFFER_content(responseContent, &receivedContent) != 0) + { + (void)BUFFER_unbuild(responseContent); + + result = HTTPAPI_ALLOC_FAILED; + LogError("(result = %s)", ENUM_TO_STRING(HTTPAPI_RESULT, result)); + } + + if (readChunk(httpHandle, (char*)receivedContent, bodyLength) < 0) + { + result = HTTPAPI_READ_DATA_FAILED; + LogError("(result = %s)", ENUM_TO_STRING(HTTPAPI_RESULT, result)); + goto exit; + } + else + { + result = HTTPAPI_OK; + } + } + else + { + (void)skipN(httpHandle, bodyLength, buf, sizeof(buf)); + result = HTTPAPI_OK; + } + } + else + { + result = HTTPAPI_OK; + } + } + else + { + size_t size = 0; + result = HTTPAPI_OK; + for (;;) + { + int chunkSize; + if (readLine(httpHandle, buf, sizeof(buf)) < 0) // read [length in hex]/r/n + { + result = HTTPAPI_READ_DATA_FAILED; + LogError("(result = %s)", ENUM_TO_STRING(HTTPAPI_RESULT, result)); + goto exit; + } + if (sscanf(buf, "%x", &chunkSize) != 1) // chunkSize is length of next line (/r/n is not counted) + { + //Cannot match string, error + result = HTTPAPI_RECEIVE_RESPONSE_FAILED; + LogError("(result = %s)", ENUM_TO_STRING(HTTPAPI_RESULT, result)); + goto exit; + } + + if (chunkSize == 0) + { + // 0 length means next line is just '\r\n' and end of chunks + if (readChunk(httpHandle, (char*)buf, 2) < 0 + || buf[0] != '\r' || buf[1] != '\n') // skip /r/n + { + (void)BUFFER_unbuild(responseContent); + + result = HTTPAPI_READ_DATA_FAILED; + LogError("(result = %s)", ENUM_TO_STRING(HTTPAPI_RESULT, result)); + goto exit; + } + break; + } + else + { + if (responseContent != NULL) + { + if (BUFFER_enlarge(responseContent, chunkSize) != 0) + { + (void)BUFFER_unbuild(responseContent); + + result = HTTPAPI_ALLOC_FAILED; + LogError("(result = %s)", ENUM_TO_STRING(HTTPAPI_RESULT, result)); + } + else if (BUFFER_content(responseContent, &receivedContent) != 0) + { + (void)BUFFER_unbuild(responseContent); + + result = HTTPAPI_ALLOC_FAILED; + LogError("(result = %s)", ENUM_TO_STRING(HTTPAPI_RESULT, result)); + } + + if (readChunk(httpHandle, (char*)receivedContent + size, chunkSize) < 0) + { + result = HTTPAPI_READ_DATA_FAILED; + LogError("(result = %s)", ENUM_TO_STRING(HTTPAPI_RESULT, result)); + goto exit; + } + } + else + { + if (skipN(httpHandle, chunkSize, buf, sizeof(buf)) < 0) + { + result = HTTPAPI_READ_DATA_FAILED; + LogError("(result = %s)", ENUM_TO_STRING(HTTPAPI_RESULT, result)); + goto exit; + } + } + + if (readChunk(httpHandle, (char*)buf, 2) < 0 + || buf[0] != '\r' || buf[1] != '\n') // skip /r/n + { + result = HTTPAPI_READ_DATA_FAILED; + LogError("(result = %s)", ENUM_TO_STRING(HTTPAPI_RESULT, result)); + goto exit; + } + size += chunkSize; + } + } + + } + +exit: + if ((handle != NULL) && + (handle->is_io_error != 0)) + { + xio_close(handle->xio_handle, NULL, NULL); + handle->is_connected = 0; + } + + return result; +} + +HTTPAPI_RESULT HTTPAPI_SetOption(HTTP_HANDLE handle, const char* optionName, const void* value) +{ + HTTPAPI_RESULT result; + if ( + (handle == NULL) || + (optionName == NULL) || + (value == NULL) + ) + { + result = HTTPAPI_INVALID_ARG; + LogError("invalid parameter (NULL) passed to HTTPAPI_SetOption"); + } + else if (strcmp("TrustedCerts", optionName) == 0) + { + HTTP_HANDLE_DATA* h = (HTTP_HANDLE_DATA*)handle; + if (h->certificate) + { + free(h->certificate); + } + + int len = strlen((char*)value); + h->certificate = (char*)malloc(len + 1); + if (h->certificate == NULL) + { + result = HTTPAPI_ERROR; + LogError("unable to allocate certificate memory in HTTPAPI_SetOption"); + } + else + { + (void)strcpy(h->certificate, (const char*)value); + result = HTTPAPI_OK; + } + } + else + { + result = HTTPAPI_INVALID_ARG; + LogError("unknown option %s", optionName); + } + return result; +} + +HTTPAPI_RESULT HTTPAPI_CloneOption(const char* optionName, const void* value, const void** savedValue) +{ + HTTPAPI_RESULT result; + if ( + (optionName == NULL) || + (value == NULL) || + (savedValue == NULL) + ) + { + result = HTTPAPI_INVALID_ARG; + LogError("invalid argument(NULL) passed to HTTPAPI_CloneOption"); + } + else if (strcmp("TrustedCerts", optionName) == 0) + { + size_t certLen = strlen((const char*)value); + char* tempCert = (char*)malloc(certLen+1); + if (tempCert == NULL) + { + result = HTTPAPI_INVALID_ARG; + LogError("unable to allocate certificate memory in HTTPAPI_CloneOption"); + } + else + { + (void)strcpy(tempCert, (const char*)value); + *savedValue = tempCert; + result = HTTPAPI_OK; + } + } + else + { + result = HTTPAPI_INVALID_ARG; + LogError("unknown option %s", optionName); + } + return result; +}