Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Diff: deps/uhttp/src/uhttp.c
- Revision:
- 0:f7f1f0d76dd6
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/deps/uhttp/src/uhttp.c Thu Aug 23 06:52:14 2018 +0000 @@ -0,0 +1,1466 @@ +// 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> +#include <stddef.h> +#include <stdbool.h> +#include <ctype.h> + +#include "azure_c_shared_utility/umock_c_prod.h" +#include "azure_c_shared_utility/gballoc.h" + +#include <string.h> +#include "azure_uhttp_c/uhttp.h" +#include "azure_c_shared_utility/httpheaders.h" +#include "azure_c_shared_utility/crt_abstractions.h" +#include "azure_c_shared_utility/strings.h" +#include "azure_c_shared_utility/xlogging.h" +#include "azure_c_shared_utility/buffer_.h" +#include "azure_c_shared_utility/singlylinkedlist.h" +#include "azure_c_shared_utility/shared_util_options.h" +#include "azure_c_shared_utility/optimize_size.h" + +#define MAX_HOSTNAME 64 +#define TIME_MAX_BUFFER 16 +#define HTTP_CRLF_LEN 2 +#define HTTP_END_TOKEN_LEN 4 + +static const char* HTTP_REQUEST_LINE_FMT = "%s %s HTTP/1.1\r\n"; +static const char* HTTP_HOST = "Host"; +static const char* HTTP_CONTENT_LEN = "content-length"; +static const char* HTTP_TRANSFER_ENCODING = "transfer-encoding"; +//static const char* HTTP_CHUNKED_ENCODING_HDR = "Transfer-Encoding: chunked\r\n"; +static const char* HTTP_CRLF_VALUE = "\r\n"; +//static const char* FORMAT_HEX_CHAR = "0x%02x "; + +typedef enum RESPONSE_MESSAGE_STATE_TAG +{ + state_initial, + state_opening, + state_open, + state_process_status_line, + state_process_headers, + state_process_body, + state_process_chunked_body, + + state_send_user_callback, + state_parse_complete, + + state_closing, + state_closed, + state_error +} RESPONSE_MESSAGE_STATE; + +typedef struct HTTP_RECV_DATA_TAG +{ + ON_HTTP_REQUEST_CALLBACK on_request_callback; + void* user_ctx; + int status_code; + RESPONSE_MESSAGE_STATE recv_state; + HTTP_HEADERS_HANDLE resp_header; + BUFFER_HANDLE msg_body; + size_t total_body_len; + BUFFER_HANDLE accrual_buff; + bool chunked_reply; +} HTTP_RECV_DATA; + +typedef struct HTTP_CLIENT_HANDLE_DATA_TAG +{ + XIO_HANDLE xio_handle; + ON_HTTP_OPEN_COMPLETE_CALLBACK on_connect; + void* connect_user_ctx; + ON_HTTP_ERROR_CALLBACK on_error; + void* error_user_ctx; + ON_HTTP_CLOSED_CALLBACK on_close_callback; + void* close_user_ctx; + HTTP_RECV_DATA recv_msg; + bool chunk_request; + bool trace_on; + bool trace_body; + char* host_name; + int port_num; + SINGLYLINKEDLIST_HANDLE data_list; + bool cert_type_ecc; + char* x509_cert; + char* x509_pk; + char* certificate; + int connected; +} HTTP_CLIENT_HANDLE_DATA; + +typedef struct HTTP_SEND_DATA_TAG +{ + HTTP_CLIENT_REQUEST_TYPE request_type; + STRING_HANDLE relative_path; + STRING_HANDLE header_line; + BUFFER_HANDLE content; +} HTTP_SEND_DATA; + +static void send_complete_callback(void* context, IO_SEND_RESULT send_result) +{ + (void)context;(void)send_result; +} + +static int initialize_received_data(HTTP_CLIENT_HANDLE_DATA* http_data) +{ + int result = 0; + + // Initialize data if neccessary + if (http_data->recv_msg.resp_header == NULL) + { + http_data->recv_msg.resp_header = HTTPHeaders_Alloc(); + if (http_data->recv_msg.resp_header == NULL) + { + /* Codes_SRS_UHTTP_07_048: [ If any error is encountered on_bytes_received shall set the state to error. ] */ + LogError("Failure creating Http header."); + result = __FAILURE__; + } + } + if (result == 0 && http_data->recv_msg.accrual_buff == NULL) + { + http_data->recv_msg.accrual_buff = BUFFER_new(); + if (http_data->recv_msg.accrual_buff == NULL) + { + /* Codes_SRS_UHTTP_07_048: [ If any error is encountered on_bytes_received shall set the state to error. ] */ + LogError("Failure creating accrual buffer."); + result = __FAILURE__; + } + } + http_data->recv_msg.chunked_reply = false; + return result; +} + +static int process_status_code_line(const unsigned char* buffer, size_t len, size_t* position, int* statusLen) +{ + int result = __FAILURE__; + size_t index; + int spaceFound = 0; + const char* initSpace = NULL; + char status_code[4]; + + for (index = 0; index < len; index++) + { + if (buffer[index] == ' ') + { + if (spaceFound == 1) + { + strncpy(status_code, initSpace, 3); + status_code[3] = '\0'; + } + else + { + initSpace = (const char*)buffer+index+1; + } + spaceFound++; + } + else if (buffer[index] == '\n') + { + *statusLen = (int)atol(status_code); + if (index < len) + { + *position = index+1; + } + else + { + *position = index; + } + result = 0; + break; + } + } + return result; +} + +static int process_header_line(const unsigned char* buffer, size_t len, size_t* position, HTTP_HEADERS_HANDLE resp_header, size_t* contentLen, bool* isChunked) +{ + int result = __FAILURE__; + size_t index; + const unsigned char* targetPos = buffer; + bool crlfEncounted = false; + bool colonEncountered = false; + char* headerKey = NULL; + bool continueProcessing = true; + + for (index = 0; index < len && continueProcessing; index++) + { + if (buffer[index] == ':' && !colonEncountered) + { + colonEncountered = true; + size_t keyLen = (&buffer[index])-targetPos; + headerKey = (char*)malloc(keyLen+1); + if (headerKey == NULL) + { + result = __FAILURE__; + continueProcessing = false; + } + else + { + memcpy(headerKey, targetPos, keyLen); + headerKey[keyLen] = '\0'; + + // Convert to lower case + for (size_t inner = 0; inner < keyLen; inner++) + { + headerKey[inner] = (char)tolower(headerKey[inner]); + } + + targetPos = buffer+index+1; + crlfEncounted = false; + } + } + else if (buffer[index] == '\r') + { + if (headerKey != NULL) + { + // Remove leading spaces + while (*targetPos == 32) { targetPos++; } + + size_t valueLen = (&buffer[index])-targetPos; + char* headerValue = (char*)malloc(valueLen+1); + if (headerValue == NULL) + { + result = __FAILURE__; + continueProcessing = false; + } + else + { + memcpy(headerValue, targetPos, valueLen); + headerValue[valueLen] = '\0'; + + if (HTTPHeaders_AddHeaderNameValuePair(resp_header, headerKey, headerValue) != HTTP_HEADERS_OK) + { + result = __FAILURE__; + continueProcessing = false; + } + else + { + if (strcmp(headerKey, HTTP_CONTENT_LEN) == 0) + { + *isChunked = false; + *contentLen = atol(headerValue); + } + else if (strcmp(headerKey, HTTP_TRANSFER_ENCODING) == 0) + { + *isChunked = true; + *contentLen = 0; + } + if (index < len) + { + *position = index; + } + else + { + *position = index-1; + } + } + } + free(headerKey); + headerKey = NULL; + free(headerValue); + } + } + else if (buffer[index] == '\n') + { + if (crlfEncounted) + { + if (index < len) + { + *position = index+1; + } + else + { + *position = index; + } + result = 0; + break; + } + else + { + colonEncountered = false; + crlfEncounted = true; + targetPos = buffer+index+1; + } + } + else + { + crlfEncounted = false; + } + } + if (headerKey != NULL) + { + free(headerKey); + } + return result; +} + +static int write_text_line(HTTP_CLIENT_HANDLE_DATA* http_data, const char* text_line) +{ + int result; + if (xio_send(http_data->xio_handle, text_line, strlen(text_line), send_complete_callback, NULL) != 0) + { + LogError("Failure calling xio_send."); + result = __FAILURE__; + } + else + { + result = 0; + if (http_data->trace_on) + { + LOG(AZ_LOG_TRACE, LOG_LINE, "%s", text_line); + } + } + return result; +} + +static int write_data_line(HTTP_CLIENT_HANDLE_DATA* http_data, const unsigned char* data_line, size_t length) +{ + int result; + if (xio_send(http_data->xio_handle, data_line, length, send_complete_callback, NULL) != 0) + { + LogError("Failure calling xio_send."); + result = __FAILURE__; + } + else + { + result = 0; + if (http_data->trace_on) + { + if (length > 0) + { + if (http_data->trace_body) + { + LOG(AZ_LOG_TRACE, LOG_LINE, "len: %d\r\n%.*s\r\n", (int)length, (int)length, data_line); + } + else + { + LOG(AZ_LOG_TRACE, LOG_LINE, "<data> len: %d\r\n", (int)length); + } + } + else + { + LOG(AZ_LOG_TRACE, LOG_LINE, "len: %d\r\n", (int)length); + } + } + } + return result; +} + +static int convert_char_to_hex(const unsigned char* hexText, size_t len) +{ + int result = 0; + for (size_t index = 0; index < len; index++) + { + if (hexText[index] == ';') + { + break; + } + else + { + int accumulator = 0; + if (hexText[index] >= 48 && hexText[index] <= 57) + { + accumulator = hexText[index] - 48; + } + else if (hexText[index] >= 65 && hexText[index] <= 70) + { + accumulator = hexText[index] - 55; + } + else if (hexText[index] >= 97 && hexText[index] <= 102) + { + accumulator = hexText[index] - 87; + } + if (index > 0) + { + result = result << 4; + } + result += accumulator; + } + } + return result; +} + +static void on_bytes_received(void* context, const unsigned char* buffer, size_t len) +{ + HTTP_CLIENT_HANDLE_DATA* http_data = (HTTP_CLIENT_HANDLE_DATA*)context; + + if (http_data != NULL && buffer != NULL && len > 0 && http_data->recv_msg.recv_state != state_error) + { + if (http_data->recv_msg.recv_state == state_initial || http_data->recv_msg.recv_state == state_open) + { + if (initialize_received_data(http_data) != 0) + { + http_data->recv_msg.recv_state = state_error; + } + else + { + http_data->recv_msg.recv_state = state_process_status_line; + } + } + + // Put the data in the buffer + if (BUFFER_append_build(http_data->recv_msg.accrual_buff, buffer, len) != 0) + { + /* Codes_SRS_UHTTP_07_048: [ If any error is encountered on_bytes_received shall set the state to error. ] */ + LogError("Failure appending bytes to buffer."); + http_data->recv_msg.recv_state = state_error; + } + + if (http_data->recv_msg.recv_state == state_process_status_line) + { + size_t index = 0; + const unsigned char* stored_bytes = BUFFER_u_char(http_data->recv_msg.accrual_buff); + size_t stored_len = BUFFER_length(http_data->recv_msg.accrual_buff); + + int lineComplete = process_status_code_line(stored_bytes, stored_len, &index, &http_data->recv_msg.status_code); + if (lineComplete == 0 && http_data->recv_msg.status_code > 0) + { + if (BUFFER_shrink(http_data->recv_msg.accrual_buff, index, false) != 0) + { + LogError("Failure appending bytes to buffer."); + http_data->recv_msg.recv_state = state_error; + } + else + { + http_data->recv_msg.recv_state = state_process_headers; + } + } + } + + if (http_data->recv_msg.recv_state == state_process_headers) + { + size_t index = 0; + const unsigned char* stored_bytes = BUFFER_u_char(http_data->recv_msg.accrual_buff); + size_t stored_len = BUFFER_length(http_data->recv_msg.accrual_buff); + + int headerComplete = process_header_line(stored_bytes, stored_len, &index, http_data->recv_msg.resp_header, &http_data->recv_msg.total_body_len, &http_data->recv_msg.chunked_reply); + if (headerComplete == 0) + { + if (http_data->recv_msg.total_body_len == 0) + { + if (http_data->recv_msg.chunked_reply) + { + + /* Codes_SRS_UHTTP_07_054: [ If the http header does not include a content length then it indicates a chunk response. ] */ + http_data->recv_msg.recv_state = state_process_chunked_body; + } + else + { + // Content len is 0 so we are finished with the body + http_data->recv_msg.recv_state = state_send_user_callback; + } + } + else + { + http_data->recv_msg.recv_state = state_process_body; + } + } + if (index > 0) + { + if (BUFFER_shrink(http_data->recv_msg.accrual_buff, index, false) != 0) + { + LogError("Failure appending bytes to buffer."); + http_data->recv_msg.recv_state = state_error; + } + } + } + + if (http_data->recv_msg.recv_state == state_process_body) + { + if (http_data->recv_msg.total_body_len != 0) + { + size_t stored_len = BUFFER_length(http_data->recv_msg.accrual_buff); + + if ((http_data->recv_msg.total_body_len == stored_len) || (http_data->recv_msg.total_body_len == (stored_len - HTTP_END_TOKEN_LEN))) + { + if (http_data->recv_msg.msg_body != NULL) + { + BUFFER_delete(http_data->recv_msg.msg_body); + } + if ((http_data->recv_msg.msg_body = BUFFER_clone(http_data->recv_msg.accrual_buff)) == NULL) + { + LogError("Failure cloning BUFFER."); + http_data->recv_msg.recv_state = state_error; + } + else + { + http_data->recv_msg.recv_state = state_send_user_callback; + } + } + else if (stored_len > http_data->recv_msg.total_body_len) + { + LogError("Failure bytes encountered is greater then body length."); + http_data->recv_msg.recv_state = state_error; + } + } + } + + if (http_data->recv_msg.recv_state == state_process_chunked_body) + { + const unsigned char* iterator = BUFFER_u_char(http_data->recv_msg.accrual_buff); + const unsigned char* initial_pos = iterator; + const unsigned char* begin = iterator; + const unsigned char* end = iterator; + size_t accural_len = BUFFER_length(http_data->recv_msg.accrual_buff); + + /* Codes_SRS_UHTTP_07_059: [ on_bytes_received shall loop throught the stored data to find the /r/n separator. ] */ + while (iterator < (initial_pos + accural_len)) + { + if (*iterator == '\r') + { + // Don't need anything + end = iterator; + iterator++; + } + else if (*iterator == '\n') + { + size_t data_length = 0; + + /* Codes_SRS_UHTTP_07_055: [ on_bytes_received shall convert the hexs length supplied in the response to the data length of the chunked data. ] */ + size_t hex_len = end - begin; + data_length = convert_char_to_hex(begin, hex_len); + if (data_length == 0) + { + if (accural_len - (iterator - initial_pos + 1) <= HTTP_END_TOKEN_LEN) + { + http_data->recv_msg.recv_state = state_send_user_callback; + } + else + { + // Need to continue parsing + http_data->recv_msg.recv_state = state_process_headers; + } + break; + } + else if ((data_length + HTTP_CRLF_LEN) < accural_len - (iterator - initial_pos)) + { + /* Codes_SRS_UHTTP_07_056: [ After the response chunk is parsed it shall be placed in a BUFFER_HANDLE. ] */ + iterator += 1; + if (BUFFER_append_build(http_data->recv_msg.msg_body, iterator, data_length) != 0) + { + /* Codes_SRS_UHTTP_07_048: [ If any error is encountered on_bytes_received shall set the stop processing the request. ] */ + LogError("Failure building buffer for chunked data."); + http_data->recv_msg.recv_state = state_error; + } + else + { + /* Codes_SRS_UHTTP_07_060: [ if the data_length specified in the chunk is beyond the amount of data recieved, the parsing shall end and wait for more data. ] */ + if (iterator + (data_length + HTTP_CRLF_LEN) > initial_pos + accural_len) + { + LogError("Invalid length specified."); + http_data->recv_msg.recv_state = state_error; + break; + } + else if (iterator + (data_length + HTTP_CRLF_LEN) == initial_pos + accural_len) + { + if (BUFFER_shrink(http_data->recv_msg.accrual_buff, accural_len, false) != 0) + { + LogError("Failure shrinking accural buffer."); + http_data->recv_msg.recv_state = state_error; + } + break; + } + else + { + // Move the iterator beyond the data we read and the /r/n + iterator += (data_length + HTTP_CRLF_LEN); + } + + /* Codes_SRS_UHTTP_07_058: [ Once a chunk size value of 0 is encountered on_bytes_received shall call the on_request_callback with the http message ] */ + if (*iterator == '0' && (accural_len - (iterator - initial_pos + 1) <= HTTP_END_TOKEN_LEN)) + { + if (accural_len - (iterator - initial_pos + 1) <= HTTP_END_TOKEN_LEN) + { + http_data->recv_msg.recv_state = state_send_user_callback; + } + else + { + // Need to continue parsing + http_data->recv_msg.recv_state = state_process_headers; + } + break; + } + else + { + size_t shrink_len = iterator - initial_pos; + if (shrink_len > 0) + { + if (BUFFER_shrink(http_data->recv_msg.accrual_buff, shrink_len, false) != 0) + { + LogError("Failure shrinking accrual buffer."); + http_data->recv_msg.recv_state = state_error; + } + else + { + accural_len = BUFFER_length(http_data->recv_msg.accrual_buff); + initial_pos = iterator = BUFFER_u_char(http_data->recv_msg.accrual_buff); + } + } + } + } + begin = end = iterator; + } + else + { + break; + } + } + else + { + end = iterator; + iterator++; + } + } + } + + if (http_data->recv_msg.recv_state == state_send_user_callback || http_data->recv_msg.recv_state == state_error) + { + const unsigned char* reply_data = NULL; + size_t reply_len = 0; + HTTP_CALLBACK_REASON http_reason = HTTP_CALLBACK_REASON_OK; + if (http_data->recv_msg.msg_body != NULL) + { + reply_data = BUFFER_u_char(http_data->recv_msg.msg_body); + reply_len = BUFFER_length(http_data->recv_msg.msg_body); + } + if (http_data->recv_msg.recv_state == state_error) + { + http_reason = HTTP_CALLBACK_REASON_PARSING_ERROR; + } + if (http_data->trace_on) + { + LOG(AZ_LOG_TRACE, LOG_LINE, "\r\nHTTP Status: %d\r\n", http_data->recv_msg.status_code); + + // Loop through headers + size_t count; + HTTPHeaders_GetHeaderCount(http_data->recv_msg.resp_header, &count); + for (size_t index = 0; index < count; index++) + { + char* header; + if (HTTPHeaders_GetHeader(http_data->recv_msg.resp_header, index, &header) == HTTP_HEADERS_OK) + { + LOG(AZ_LOG_TRACE, LOG_LINE, "%s", header); + free(header); + } + } + if (http_data->trace_body && reply_len > 0) + { + LOG(AZ_LOG_TRACE, LOG_LINE, "\r\n%.*s\r\n", (int)reply_len, reply_data); + } + } + http_data->recv_msg.on_request_callback(http_data->recv_msg.user_ctx, http_reason, reply_data, reply_len, http_data->recv_msg.status_code, http_data->recv_msg.resp_header); + http_data->recv_msg.recv_state = state_parse_complete; + } + + if (http_data->recv_msg.recv_state == state_parse_complete) + { + HTTPHeaders_Free(http_data->recv_msg.resp_header); + http_data->recv_msg.resp_header = NULL; + BUFFER_delete(http_data->recv_msg.msg_body); + http_data->recv_msg.msg_body = NULL; + BUFFER_delete(http_data->recv_msg.accrual_buff); + http_data->recv_msg.accrual_buff = NULL; + } + } +} + +static void on_xio_close_complete(void* context) +{ + if (context != NULL) + { + /* Codes_SRS_UHTTP_07_045: [ If on_close_callback is not NULL, on_close_callback shall be called once the underlying xio is closed. ] */ + HTTP_CLIENT_HANDLE_DATA* http_data = (HTTP_CLIENT_HANDLE_DATA*)context; + if (http_data->on_close_callback) + { + http_data->on_close_callback(http_data->close_user_ctx); + } + http_data->recv_msg.recv_state = state_closed; + http_data->connected = 0; + } +} + +static void on_xio_open_complete(void* context, IO_OPEN_RESULT open_result) +{ + /* Codes_SRS_UHTTP_07_049: [ If not NULL uhttp_client_open shall call the on_connect callback with the callback_ctx, once the underlying xio's open is complete. ] */ + if (context != NULL) + { + HTTP_CLIENT_HANDLE_DATA* http_data = (HTTP_CLIENT_HANDLE_DATA*)context; + if (open_result == IO_OPEN_OK) + { + /* Codes_SRS_UHTTP_07_042: [ If the underlying socket opens successfully the on_connect callback shall be call with HTTP_CALLBACK_REASON_OK... ] */ + if (http_data->on_connect != NULL) + { + http_data->on_connect(http_data->connect_user_ctx, HTTP_CALLBACK_REASON_OK); + } + http_data->recv_msg.recv_state = state_open; + http_data->connected = 1; + } + else + { + /* Codes_SRS_UHTTP_07_043: [ Otherwise on_connect callback shall be call with HTTP_CLIENT_OPEN_REQUEST_FAILED. ] */ + if (http_data->on_connect != NULL) + { + http_data->on_connect(http_data->connect_user_ctx, HTTP_CALLBACK_REASON_OPEN_FAILED); + } + } + } + else + { + LogError("Context on_xio_open_complete is NULL"); + } +} + +static void on_io_error(void* context) +{ + /* Codes_SRS_UHTTP_07_050: [ if context is NULL on_io_error shall do nothing. ] */ + if (context != NULL) + { + HTTP_CLIENT_HANDLE_DATA* http_data = (HTTP_CLIENT_HANDLE_DATA*)context; + /* Codes_SRS_UHTTP_07_051: [ if on_error callback is not NULL, on_io_error shall call on_error callback. ] */ + if (http_data->on_error) + { + http_data->on_error(http_data->error_user_ctx, HTTP_CALLBACK_REASON_ERROR); + } + http_data->connected = 0; + } + else + { + LogError("Context on_io_error is NULL"); + } +} + +static int construct_http_headers(HTTP_HEADERS_HANDLE http_header, size_t content_len, STRING_HANDLE buffData, bool chunk_data, const char* hostname, int port_num) +{ + (void)chunk_data; + int result = 0; + size_t headerCnt = 0; + if ( (http_header != NULL) && HTTPHeaders_GetHeaderCount(http_header, &headerCnt) != HTTP_HEADERS_OK) + { + LogError("Failed in HTTPHeaders_GetHeaderCount"); + result = __FAILURE__; + } + else + { + bool hostname_found = false; + for (size_t index = 0; index < headerCnt && result == 0; index++) + { + char* header; + if (HTTPHeaders_GetHeader(http_header, index, &header) != HTTP_HEADERS_OK) + { + result = __FAILURE__; + LogError("Failed in HTTPHeaders_GetHeader"); + } + else + { + size_t dataLen = strlen(header)+2; + char* sendData = malloc(dataLen+1); + if (sendData == NULL) + { + result = __FAILURE__; + LogError("Failed in allocating header data"); + } + else + { + if (strcmp(header, HTTP_HOST) == 0) + { + hostname_found = true; + } + + if (snprintf(sendData, dataLen+1, "%s\r\n", header) <= 0) + { + result = __FAILURE__; + LogError("Failed in constructing header data"); + } + else + { + if (STRING_concat(buffData, sendData) != 0) + { + result = __FAILURE__; + LogError("Failed in building header data"); + } + } + free(sendData); + } + free(header); + } + } + if (!hostname_found) + { + size_t host_len = strlen(HTTP_HOST)+strlen(hostname)+8+2; + char* host_header = malloc(host_len+1); + if (host_header == NULL) + { + LogError("Failed allocating host header"); + result = __FAILURE__; + } + else + { + if (snprintf(host_header, host_len+1, "%s: %s:%d\r\n", HTTP_HOST, hostname, port_num) <= 0) + { + LogError("Failed constructing host header"); + result = __FAILURE__; + } + else if (STRING_concat(buffData, host_header) != 0) + { + LogError("Failed adding the host header to the http item"); + result = __FAILURE__; + } + free(host_header); + } + } + + if (result == 0) + { + /* Codes_SRS_UHTTP_07_015: [uhttp_client_execute_request shall add the Content-Length to the request if the contentLength is > 0] */ + size_t fmtLen = strlen(HTTP_CONTENT_LEN)+strlen(HTTP_CRLF_VALUE)+8; + char* content = malloc(fmtLen+1); + if (content == NULL) + { + LogError("Failed allocating chunk header"); + result = __FAILURE__; + } + else + { + /* Codes_SRS_UHTTP_07_015: [on_bytes_received shall add the Content-Length http header item to the request.] */ + if (sprintf(content, "%s: %lu%s", HTTP_CONTENT_LEN, content_len, HTTP_CRLF_VALUE) <= 0) + { + result = __FAILURE__; + LogError("Failed allocating content len header data"); + } + else + { + if (STRING_concat(buffData, content) != 0) + { + result = __FAILURE__; + LogError("Failed building content len header data"); + } + } + free(content); + } + + if (STRING_concat(buffData, "\r\n") != 0) + { + result = __FAILURE__; + LogError("Failed sending header finalization data"); + } + } + } + return result; +} + +static STRING_HANDLE construct_http_data(HTTP_CLIENT_REQUEST_TYPE request_type, const char* relative_path, STRING_HANDLE http_line) +{ + STRING_HANDLE result; + + const char* method = (request_type == HTTP_CLIENT_REQUEST_GET) ? "GET" + : (request_type == HTTP_CLIENT_REQUEST_OPTIONS) ? "OPTIONS" + : (request_type == HTTP_CLIENT_REQUEST_POST) ? "POST" + : (request_type == HTTP_CLIENT_REQUEST_PUT) ? "PUT" + : (request_type == HTTP_CLIENT_REQUEST_DELETE) ? "DELETE" + : (request_type == HTTP_CLIENT_REQUEST_PATCH) ? "PATCH" + : NULL; + /* Codes_SRS_UHTTP_07_014: [If the request_type is not a valid request http_client_execute_request shall return HTTP_CLIENT_ERROR] */ + if (method == NULL) + { + LogError("Invalid request method %s specified", method); + result = NULL; + } + else + { + size_t buffLen = strlen(HTTP_REQUEST_LINE_FMT)+strlen(method)+strlen(relative_path); + char* request = malloc(buffLen+1); + if (request == NULL) + { + result = NULL; + LogError("Failure allocating Request data"); + } + else + { + if (snprintf(request, buffLen+1, HTTP_REQUEST_LINE_FMT, method, relative_path) <= 0) + { + result = NULL; + LogError("Failure writing request buffer"); + } + else + { + result = STRING_construct(request); + if (result == NULL) + { + LogError("Failure creating buffer object"); + } + else if (STRING_concat_with_STRING(result, http_line) != 0) + { + STRING_delete(result); + result = NULL; + LogError("Failure writing request buffers"); + } + } + free(request); + } + } + return result; +} + +static int send_http_data(HTTP_CLIENT_HANDLE_DATA* http_data, HTTP_CLIENT_REQUEST_TYPE request_type, const char* relative_path, + STRING_HANDLE http_line) +{ + int result; + STRING_HANDLE transmit_data = construct_http_data(request_type, relative_path, http_line); + if (transmit_data == NULL) + { + LogError("Failure constructing http data"); + result = __FAILURE__; + } + else + { + /* Tests_SRS_UHTTP_07_016: [http_client_execute_request shall transmit the http headers data through a call to xio_send;]*/ + if (write_text_line(http_data, STRING_c_str(transmit_data) ) != 0) + { + result = __FAILURE__; + LogError("Failure writing request buffer"); + } + else + { + result = 0; + } + STRING_delete(transmit_data); + } + return result; +} + +HTTP_CLIENT_HANDLE uhttp_client_create(const IO_INTERFACE_DESCRIPTION* io_interface_desc, const void* xio_param, ON_HTTP_ERROR_CALLBACK on_http_error, void* callback_ctx) +{ + HTTP_CLIENT_HANDLE_DATA* result; + /* Codes_SRS_UHTTP_07_002: [If io_interface_desc is NULL, uhttp_client_create shall return NULL.] */ + if (io_interface_desc == NULL) + { + LogError("Invalid Parameter io_interface_desc is NULL"); + result = NULL; + } + else + { + result = malloc(sizeof(HTTP_CLIENT_HANDLE_DATA)); + if (result == NULL) + { + /* Codes_SRS_UHTTP_07_003: [If uhttp_client_create encounters any error then it shall return NULL] */ + LogError("Failure allocating http_client_handle"); + } + else + { + memset(result, 0, sizeof(HTTP_CLIENT_HANDLE_DATA) ); + if ((result->data_list = singlylinkedlist_create() ) == NULL) + { + /* Codes_SRS_UHTTP_07_003: [If uhttp_client_create encounters any error then it shall return NULL] */ + LogError("Failure allocating data list"); + free(result); + result = NULL; + } + else if ((result->xio_handle = xio_create(io_interface_desc, xio_param) ) == NULL) + { + /* Codes_SRS_UHTTP_07_044: [ if a failure is encountered on xio_open uhttp_client_open shall return HTTP_CLIENT_OPEN_REQUEST_FAILED. ] */ + LogError("xio create failed"); + singlylinkedlist_destroy(result->data_list); + free(result); + result = NULL; + } + else + { + /* Codes_SRS_UHTTP_07_001: [uhttp_client_create shall return an initialize the http client handle.] */ + result->on_error = on_http_error; + result->error_user_ctx = callback_ctx; + result->recv_msg.recv_state = state_initial; + result->chunk_request = false; + result->trace_on = false; + bool ignore_check = true; + (void)xio_setoption(result->xio_handle, "ignore_server_name_check", &ignore_check); + } + } + } + return (HTTP_CLIENT_HANDLE)result; +} + +void uhttp_client_destroy(HTTP_CLIENT_HANDLE handle) +{ + /* Codes_SRS_UHTTP_07_004: [ If handle is NULL then uhttp_client_destroy shall do nothing ] */ + if (handle != NULL) + { + /* Codes_SRS_UHTTP_07_005: [uhttp_client_destroy shall free any resource that is allocated in this translation unit] */ + singlylinkedlist_destroy(handle->data_list); + xio_destroy(handle->xio_handle); + free(handle->certificate); + free(handle->x509_pk); + free(handle->x509_cert); + free(handle); + } +} + +HTTP_CLIENT_RESULT uhttp_client_open(HTTP_CLIENT_HANDLE handle, const char* host, int port_num, ON_HTTP_OPEN_COMPLETE_CALLBACK on_connect, void* callback_ctx) +{ + HTTP_CLIENT_RESULT result; + if (handle == NULL || host == NULL) + { + /* Codes_SRS_UHTTP_07_006: [If handle, io_interface_desc or host is NULL then `uhttp_client_open` shall return HTTP_CLIENT_INVALID_ARG] */ + LogError("Invalid handle value"); + result = HTTP_CLIENT_INVALID_ARG; + } + else + { + HTTP_CLIENT_HANDLE_DATA* http_data = (HTTP_CLIENT_HANDLE_DATA*)handle; + + if (http_data->recv_msg.recv_state != state_initial && http_data->recv_msg.recv_state != state_error && http_data->recv_msg.recv_state != state_closed) + { + LogError("Unable to open previously open client."); + result = HTTP_CLIENT_INVALID_STATE; + } + else if (mallocAndStrcpy_s(&http_data->host_name, host) != 0) + { + LogError("copying hostname has failed"); + result = HTTP_CLIENT_ERROR; + } + /* Codes_SRS_UHTTP_07_007: [http_client_connect shall attempt to open the xio_handle. ] */ + else + { + result = HTTP_CLIENT_OK; + http_data->recv_msg.recv_state = state_opening; + http_data->on_connect = on_connect; + http_data->connect_user_ctx = callback_ctx; + http_data->port_num = port_num; + + if (http_data->x509_cert != NULL && http_data->x509_pk != NULL) + { + if (xio_setoption(http_data->xio_handle, SU_OPTION_X509_CERT, http_data->x509_cert) != 0 || xio_setoption(http_data->xio_handle, SU_OPTION_X509_PRIVATE_KEY, http_data->x509_pk) != 0) + { + LogError("Failed setting x509 certificate"); + result = HTTP_CLIENT_ERROR; + free(http_data->host_name); + http_data->host_name = NULL; + http_data->on_connect = NULL; + http_data->connect_user_ctx = NULL; + http_data->port_num = 0; + } + } + if (result == HTTP_CLIENT_OK && http_data->certificate != NULL) + { + if (xio_setoption(http_data->xio_handle, OPTION_TRUSTED_CERT, http_data->certificate) != 0) + { + LogError("Failed setting Trusted certificate"); + result = HTTP_CLIENT_ERROR; + free(http_data->host_name); + http_data->host_name = NULL; + http_data->on_connect = NULL; + http_data->connect_user_ctx = NULL; + http_data->port_num = 0; + } + } + + if (result == HTTP_CLIENT_OK) + { +#ifdef USE_OPENSSL + // Default to tls 1.2 + int tls_version = 12; + xio_setoption(http_data->xio_handle, OPTION_TLS_VERSION, &tls_version); +#endif + if (xio_open(http_data->xio_handle, on_xio_open_complete, http_data, on_bytes_received, http_data, on_io_error, http_data) != 0) + { + /* Codes_SRS_UHTTP_07_044: [ if a failure is encountered on xio_open uhttp_client_open shall return HTTP_CLIENT_OPEN_REQUEST_FAILED. ] */ + LogError("opening xio failed"); + free(http_data->host_name); + http_data->host_name = NULL; + http_data->on_connect = NULL; + http_data->connect_user_ctx = NULL; + http_data->port_num = 0; + + result = HTTP_CLIENT_OPEN_FAILED; + } + else + { + /* Codes_SRS_UHTTP_07_008: [If http_client_connect succeeds then it shall return HTTP_CLIENT_OK] */ + result = HTTP_CLIENT_OK; + } + } + } + } + return result; +} + +void uhttp_client_close(HTTP_CLIENT_HANDLE handle, ON_HTTP_CLOSED_CALLBACK on_close_callback, void* callback_ctx) +{ + HTTP_CLIENT_HANDLE_DATA* http_data = (HTTP_CLIENT_HANDLE_DATA*)handle; + /* Codes_SRS_UHTTP_07_009: [If handle is NULL then http_client_close shall do nothing] */ + /* Codes_SRS_UHTTP_07_049: [ If the state has been previously set to state_closed, uhttp_client_close shall do nothing. ] */ + if (http_data != NULL && http_data->recv_msg.recv_state != state_closed && http_data->recv_msg.recv_state != state_closing) + { + http_data->on_close_callback = on_close_callback; + http_data->close_user_ctx = callback_ctx; + /* Codes_SRS_UHTTP_07_010: [If the xio_handle is NOT NULL http_client_close shall call xio_close to close the handle] */ + (void)xio_close(http_data->xio_handle, on_xio_close_complete, http_data); + + LIST_ITEM_HANDLE pending_list_item; + while ((pending_list_item = singlylinkedlist_get_head_item(http_data->data_list)) != NULL) + { + HTTP_SEND_DATA* send_data = (HTTP_SEND_DATA*)singlylinkedlist_item_get_value(pending_list_item); + if (send_data != NULL) + { + STRING_delete(send_data->relative_path); + BUFFER_delete(send_data->content); + STRING_delete(send_data->header_line); + free(send_data); + } + singlylinkedlist_remove(http_data->data_list, pending_list_item); + } + + http_data->recv_msg.status_code = 0; + http_data->recv_msg.recv_state = state_closing; + http_data->recv_msg.total_body_len = 0; + free(http_data->host_name); + http_data->host_name = NULL; + + /* Codes_SRS_UHTTP_07_011: [http_client_close shall free any HTTPHeader object that has not been freed] */ + if (http_data->recv_msg.resp_header != NULL) + { + HTTPHeaders_Free(http_data->recv_msg.resp_header); + http_data->recv_msg.resp_header = NULL; + } + if (http_data->recv_msg.msg_body != NULL) + { + BUFFER_delete(http_data->recv_msg.msg_body); + http_data->recv_msg.msg_body = NULL; + } + } +} + +HTTP_CLIENT_RESULT uhttp_client_execute_request(HTTP_CLIENT_HANDLE handle, HTTP_CLIENT_REQUEST_TYPE request_type, const char* relative_path, + HTTP_HEADERS_HANDLE http_header_handle, const unsigned char* content, size_t content_len, ON_HTTP_REQUEST_CALLBACK on_request_callback, void* callback_ctx) +{ + HTTP_CLIENT_RESULT result; + LIST_ITEM_HANDLE list_item; + + /* Codes_SRS_UHTTP_07_012: [If handle, relativePath, or httpHeadersHandle is NULL then http_client_execute_request shall return HTTP_CLIENT_INVALID_ARG] */ + if (handle == NULL || on_request_callback == NULL || + (content != NULL && content_len == 0) || (content == NULL && content_len != 0) ) + { + result = HTTP_CLIENT_INVALID_ARG; + LogError("Invalid parameter sent to execute_request"); + } + else + { + HTTP_CLIENT_HANDLE_DATA* http_data = (HTTP_CLIENT_HANDLE_DATA*)handle; + + http_data->recv_msg.status_code = 0; + http_data->recv_msg.recv_state = state_initial; + http_data->recv_msg.total_body_len = 0; + http_data->recv_msg.on_request_callback = on_request_callback; + http_data->recv_msg.user_ctx = callback_ctx; + if (http_data->recv_msg.resp_header != NULL) + { + HTTPHeaders_Free(http_data->recv_msg.resp_header); + } + if (http_data->recv_msg.msg_body != NULL) + { + BUFFER_delete(http_data->recv_msg.msg_body); + } + if ((http_data->recv_msg.resp_header = HTTPHeaders_Alloc()) == NULL) + { + /* Codes_SRS_UHTTP_07_017: [If any failure encountered http_client_execute_request shall return HTTP_CLIENT_ERROR] */ + LogError("Failure allocating http http_data items"); + result = HTTP_CLIENT_ERROR; + } + else if ( (http_data->recv_msg.msg_body = BUFFER_new() ) == NULL) + { + /* Codes_SRS_UHTTP_07_017: [If any failure encountered http_client_execute_request shall return HTTP_CLIENT_ERROR] */ + LogError("Failure allocating http data items"); + HTTPHeaders_Free(http_data->recv_msg.resp_header); + http_data->recv_msg.resp_header = NULL; + result = HTTP_CLIENT_ERROR; + } + else + { + HTTP_SEND_DATA* send_data = (HTTP_SEND_DATA*)malloc(sizeof(HTTP_SEND_DATA)); + if (send_data == NULL) + { + LogError("Failure allocating http data items"); + BUFFER_delete(http_data->recv_msg.msg_body); + http_data->recv_msg.msg_body = NULL; + HTTPHeaders_Free(http_data->recv_msg.resp_header); + http_data->recv_msg.resp_header = NULL; + result = HTTP_CLIENT_ERROR; + } + else + { + memset(send_data, 0, sizeof(HTTP_SEND_DATA)); + /* Codes_SRS_UHTTP_07_041: [HTTP_CLIENT_REQUEST_TYPE shall support all request types specified under section 9.1.2 in the spec.] */ + send_data->request_type = request_type; + if ( (content_len > 0) && (send_data->content = BUFFER_create(content, content_len)) == NULL) + { + LogError("Failure allocating content buffer"); + result = HTTP_CLIENT_ERROR; + BUFFER_delete(http_data->recv_msg.msg_body); + http_data->recv_msg.msg_body = NULL; + HTTPHeaders_Free(http_data->recv_msg.resp_header); + http_data->recv_msg.resp_header = NULL; + free(send_data); + } + else if ((send_data->header_line = STRING_new()) == NULL) + { + LogError("Failure allocating content buffer"); + result = HTTP_CLIENT_ERROR; + BUFFER_delete(send_data->content); + BUFFER_delete(http_data->recv_msg.msg_body); + http_data->recv_msg.msg_body = NULL; + HTTPHeaders_Free(http_data->recv_msg.resp_header); + http_data->recv_msg.resp_header = NULL; + free(send_data); + } + else if (construct_http_headers(http_header_handle, content_len, send_data->header_line, false, http_data->host_name, http_data->port_num)) + { + LogError("Failure allocating content buffer"); + result = HTTP_CLIENT_ERROR; + BUFFER_delete(send_data->content); + STRING_delete(send_data->header_line); + BUFFER_delete(http_data->recv_msg.msg_body); + http_data->recv_msg.msg_body = NULL; + HTTPHeaders_Free(http_data->recv_msg.resp_header); + http_data->recv_msg.resp_header = NULL; + free(send_data); + } + else if ((list_item = singlylinkedlist_add(http_data->data_list, send_data)) == NULL) + { + STRING_delete(send_data->header_line); + BUFFER_delete(send_data->content); + LogError("Failure adding send data to list"); + result = HTTP_CLIENT_ERROR; + BUFFER_delete(http_data->recv_msg.msg_body); + http_data->recv_msg.msg_body = NULL; + HTTPHeaders_Free(http_data->recv_msg.resp_header); + http_data->recv_msg.resp_header = NULL; + free(send_data); + } + else + { + if (relative_path != NULL) + { + if ((send_data->relative_path = STRING_construct(relative_path)) == NULL) + { + (void)singlylinkedlist_remove(http_data->data_list, list_item); + STRING_delete(send_data->header_line); + BUFFER_delete(send_data->content); + LogError("Failure allocating relative path buffer"); + BUFFER_delete(http_data->recv_msg.msg_body); + http_data->recv_msg.msg_body = NULL; + HTTPHeaders_Free(http_data->recv_msg.resp_header); + http_data->recv_msg.resp_header = NULL; + free(send_data); + result = HTTP_CLIENT_ERROR; + } + else + { + /* Codes_SRS_UHTTP_07_018: [upon success http_client_execute_request shall return HTTP_CLIENT_OK.] */ + result = HTTP_CLIENT_OK; + } + } + else + { + if ((send_data->relative_path = STRING_construct("/")) == NULL) + { + (void)singlylinkedlist_remove(http_data->data_list, list_item); + STRING_delete(send_data->header_line); + BUFFER_delete(send_data->content); + LogError("Failure allocating relative path buffer"); + BUFFER_delete(http_data->recv_msg.msg_body); + http_data->recv_msg.msg_body = NULL; + HTTPHeaders_Free(http_data->recv_msg.resp_header); + http_data->recv_msg.resp_header = NULL; + free(send_data); + result = HTTP_CLIENT_ERROR; + } + else + { + /* Codes_SRS_UHTTP_07_018: [upon success http_client_execute_request shall return HTTP_CLIENT_OK.] */ + result = HTTP_CLIENT_OK; + } + } + } + } + } + } + return result; +} + +void uhttp_client_dowork(HTTP_CLIENT_HANDLE handle) +{ + if (handle != NULL) + { + /* Codes_SRS_UHTTP_07_037: [http_client_dowork shall call the underlying xio_dowork function. ] */ + HTTP_CLIENT_HANDLE_DATA* http_data = (HTTP_CLIENT_HANDLE_DATA*)handle; + xio_dowork(http_data->xio_handle); + + // Wait till I'm connected + if (handle->connected == 1) + { + LIST_ITEM_HANDLE pending_list_item; + /* Codes_SRS_UHTTP_07_016: [http_client_dowork shall iterate through the queued Data using the xio interface to send the http request in the following ways...] */ + while ((pending_list_item = singlylinkedlist_get_head_item(http_data->data_list)) != NULL) + { + HTTP_SEND_DATA* send_data = (HTTP_SEND_DATA*)singlylinkedlist_item_get_value(pending_list_item); + if (send_data != NULL) + { + size_t content_len = BUFFER_length(send_data->content); + /* Codes_SRS_UHTTP_07_052: [uhttp_client_dowork shall call xio_send to transmits the header information... ] */ + if (send_http_data(http_data, send_data->request_type, STRING_c_str(send_data->relative_path), send_data->header_line) != 0) + { + LogError("Failure writing content buffer"); + if (http_data->on_error) + { + http_data->on_error(http_data->error_user_ctx, HTTP_CALLBACK_REASON_SEND_FAILED); + } + } + else if (content_len > 0) + { + /* Codes_SRS_UHTTP_07_053: [ Then uhttp_client_dowork shall use xio_send to transmit the content of the http request. ] */ + if (write_data_line(http_data, BUFFER_u_char(send_data->content), content_len) != 0) + { + LogError("Failure writing content buffer"); + if (http_data->on_error) + { + http_data->on_error(http_data->error_user_ctx, HTTP_CALLBACK_REASON_SEND_FAILED); + } + } + } + + /* Codes_SRS_UHTTP_07_046: [ http_client_dowork shall free resouces queued to send to the http endpoint. ] */ + STRING_delete(send_data->relative_path); + BUFFER_delete(send_data->content); + STRING_delete(send_data->header_line); + free(send_data); + } + (void)singlylinkedlist_remove(http_data->data_list, pending_list_item); + } + } + } +} + +HTTP_CLIENT_RESULT uhttp_client_set_trace(HTTP_CLIENT_HANDLE handle, bool trace_on, bool trace_data) +{ + HTTP_CLIENT_RESULT result; + if (handle == NULL) + { + /* Codes_SRS_UHTTP_07_038: [If handle is NULL then http_client_set_trace shall return HTTP_CLIENT_INVALID_ARG] */ + result = HTTP_CLIENT_INVALID_ARG; + LogError("invalid parameter (NULL) passed to http_client_set_trace"); + } + else + { + /* Codes_SRS_UHTTP_07_039: [http_client_set_trace shall set the HTTP tracing to the trace_on variable.] */ + handle->trace_on = trace_on; + handle->trace_body = trace_data; + /* Codes_SRS_UHTTP_07_040: [if http_client_set_trace finishes successfully then it shall return HTTP_CLIENT_OK.] */ + result = HTTP_CLIENT_OK; + } + return result; +} + +HTTP_CLIENT_RESULT uhttp_client_set_X509_cert(HTTP_CLIENT_HANDLE handle, bool ecc_type, const char* certificate, const char* private_key) +{ + HTTP_CLIENT_RESULT result; + if (handle == NULL || certificate == NULL || private_key == NULL) + { + /* Codes_SRS_UHTTP_07_038: [If handle is NULL then http_client_set_trace shall return HTTP_CLIENT_INVALID_ARG] */ + result = HTTP_CLIENT_INVALID_ARG; + LogError("invalid parameter handle: %p certificate: %p private_key: %p", handle, certificate, private_key); + } + else if (handle->recv_msg.recv_state != state_initial) + { + result = HTTP_CLIENT_INVALID_STATE; + LogError("You must set the X509 certificates before opening the connection"); + } + else + { + handle->cert_type_ecc = ecc_type; + if (mallocAndStrcpy_s(&handle->x509_cert, certificate) != 0) + { + result = HTTP_CLIENT_ERROR; + LogError("failure allocating certificate"); + } + else if (mallocAndStrcpy_s(&handle->x509_pk, private_key) != 0) + { + free(handle->x509_cert); + handle->x509_cert = NULL; + result = HTTP_CLIENT_ERROR; + LogError("failure allocating private key"); + } + else + { + result = HTTP_CLIENT_OK; + } + } + return result; +} + +HTTP_CLIENT_RESULT uhttp_client_set_trusted_cert(HTTP_CLIENT_HANDLE handle, const char* certificate) +{ + HTTP_CLIENT_RESULT result; + if (handle == NULL || certificate == NULL) + { + /* Codes_SRS_UHTTP_07_038: [If handle is NULL then http_client_set_trace shall return HTTP_CLIENT_INVALID_ARG] */ + result = HTTP_CLIENT_INVALID_ARG; + LogError("invalid parameter handle: %p certificate: %p", handle, certificate); + } + else if (handle->recv_msg.recv_state != state_initial) + { + result = HTTP_CLIENT_INVALID_STATE; + LogError("You must set the certificates before opening the connection"); + } + else + { + if (mallocAndStrcpy_s(&handle->certificate, certificate) != 0) + { + result = HTTP_CLIENT_ERROR; + LogError("failure allocating certificate"); + } + else + { + result = HTTP_CLIENT_OK; + } + } + return result; +} + +const char* uhttp_client_get_trusted_cert(HTTP_CLIENT_HANDLE handle) +{ + const char* result; + if (handle == NULL) + { + result = NULL; + LogError("invalid parameter NULL handle"); + } + else + { + result = handle->certificate; + } + return result; +} + +HTTP_CLIENT_RESULT uhttp_client_set_option(HTTP_CLIENT_HANDLE handle, const char* optionName, const void* value) +{ + HTTP_CLIENT_RESULT result; + if (handle == NULL) + { + /* Codes_SRS_UHTTP_07_038: [If handle is NULL then http_client_set_trace shall return HTTP_CLIENT_INVALID_ARG] */ + result = HTTP_CLIENT_INVALID_ARG; + LogError("invalid parameter handle: %p", handle); + } + else + { + int setoption_result = xio_setoption(handle->xio_handle, optionName, value); + if (setoption_result != 0) + { + LogError("xio_setoption fails, returns %d", setoption_result); + result = HTTP_CLIENT_ERROR; + } + else + { + result = HTTP_CLIENT_OK; + } + + } + + return result; +}