Azure IoT common library

Fork of azure_c_shared_utility by Azure IoT

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;
+}