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.
c-utility/src/http_proxy_io.c
- Committer:
- XinZhangMS
- Date:
- 2018-08-23
- Revision:
- 0:f7f1f0d76dd6
File content as of revision 0:f7f1f0d76dd6:
// 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 <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <limits.h>
#include <stddef.h>
#include "azure_c_shared_utility/gballoc.h"
#include "azure_c_shared_utility/xio.h"
#include "azure_c_shared_utility/socketio.h"
#include "azure_c_shared_utility/crt_abstractions.h"
#include "azure_c_shared_utility/http_proxy_io.h"
#include "azure_c_shared_utility/base64.h"
typedef enum HTTP_PROXY_IO_STATE_TAG
{
HTTP_PROXY_IO_STATE_CLOSED,
HTTP_PROXY_IO_STATE_OPENING_UNDERLYING_IO,
HTTP_PROXY_IO_STATE_WAITING_FOR_CONNECT_RESPONSE,
HTTP_PROXY_IO_STATE_OPEN,
HTTP_PROXY_IO_STATE_CLOSING,
HTTP_PROXY_IO_STATE_ERROR
} HTTP_PROXY_IO_STATE;
typedef struct HTTP_PROXY_IO_INSTANCE_TAG
{
HTTP_PROXY_IO_STATE http_proxy_io_state;
ON_BYTES_RECEIVED on_bytes_received;
void* on_bytes_received_context;
ON_IO_ERROR on_io_error;
void* on_io_error_context;
ON_IO_OPEN_COMPLETE on_io_open_complete;
void* on_io_open_complete_context;
ON_IO_CLOSE_COMPLETE on_io_close_complete;
void* on_io_close_complete_context;
char* hostname;
int port;
char* proxy_hostname;
int proxy_port;
char* username;
char* password;
XIO_HANDLE underlying_io;
unsigned char* receive_buffer;
size_t receive_buffer_size;
} HTTP_PROXY_IO_INSTANCE;
static CONCRETE_IO_HANDLE http_proxy_io_create(void* io_create_parameters)
{
HTTP_PROXY_IO_INSTANCE* result;
if (io_create_parameters == NULL)
{
/* Codes_SRS_HTTP_PROXY_IO_01_002: [ If `io_create_parameters` is NULL, `http_proxy_io_create` shall fail and return NULL. ]*/
result = NULL;
LogError("NULL io_create_parameters.");
}
else
{
/* Codes_SRS_HTTP_PROXY_IO_01_003: [ `io_create_parameters` shall be used as an `HTTP_PROXY_IO_CONFIG*`. ]*/
HTTP_PROXY_IO_CONFIG* http_proxy_io_config = (HTTP_PROXY_IO_CONFIG*)io_create_parameters;
if ((http_proxy_io_config->hostname == NULL) ||
(http_proxy_io_config->proxy_hostname == NULL))
{
/* Codes_SRS_HTTP_PROXY_IO_01_004: [ If the `hostname` or `proxy_hostname` member is NULL, then `http_proxy_io_create` shall fail and return NULL. ]*/
result = NULL;
LogError("Bad arguments: hostname = %p, proxy_hostname = %p",
http_proxy_io_config->hostname, http_proxy_io_config->proxy_hostname);
}
/* Codes_SRS_HTTP_PROXY_IO_01_095: [ If one of the fields `username` and `password` is non-NULL, then the other has to be also non-NULL, otherwise `http_proxy_io_create` shall fail and return NULL. ]*/
else if (((http_proxy_io_config->username == NULL) && (http_proxy_io_config->password != NULL)) ||
((http_proxy_io_config->username != NULL) && (http_proxy_io_config->password == NULL)))
{
result = NULL;
LogError("Bad arguments: username = %p, password = %p",
http_proxy_io_config->username, http_proxy_io_config->password);
}
else
{
/* Codes_SRS_HTTP_PROXY_IO_01_001: [ `http_proxy_io_create` shall create a new instance of the HTTP proxy IO. ]*/
result = (HTTP_PROXY_IO_INSTANCE*)malloc(sizeof(HTTP_PROXY_IO_INSTANCE));
if (result == NULL)
{
/* Codes_SRS_HTTP_PROXY_IO_01_051: [ If allocating memory for the new instance fails, `http_proxy_io_create` shall fail and return NULL. ]*/
LogError("Failed allocating HTTP proxy IO instance.");
}
else
{
/* Codes_SRS_HTTP_PROXY_IO_01_005: [ `http_proxy_io_create` shall copy the `hostname`, `port`, `username` and `password` values for later use when the actual CONNECT is performed. ]*/
/* Codes_SRS_HTTP_PROXY_IO_01_006: [ `hostname` and `proxy_hostname`, `username` and `password` shall be copied by calling `mallocAndStrcpy_s`. ]*/
if (mallocAndStrcpy_s(&result->hostname, http_proxy_io_config->hostname) != 0)
{
/* Codes_SRS_HTTP_PROXY_IO_01_007: [ If `mallocAndStrcpy_s` fails then `http_proxy_io_create` shall fail and return NULL. ]*/
LogError("Failed to copy the hostname.");
/* Codes_SRS_HTTP_PROXY_IO_01_008: [ When `http_proxy_io_create` fails, all allocated resources up to that point shall be freed. ]*/
free(result);
result = NULL;
}
else
{
/* Codes_SRS_HTTP_PROXY_IO_01_006: [ `hostname` and `proxy_hostname`, `username` and `password` shall be copied by calling `mallocAndStrcpy_s`. ]*/
if (mallocAndStrcpy_s(&result->proxy_hostname, http_proxy_io_config->proxy_hostname) != 0)
{
/* Codes_SRS_HTTP_PROXY_IO_01_007: [ If `mallocAndStrcpy_s` fails then `http_proxy_io_create` shall fail and return NULL. ]*/
LogError("Failed to copy the proxy_hostname.");
/* Codes_SRS_HTTP_PROXY_IO_01_008: [ When `http_proxy_io_create` fails, all allocated resources up to that point shall be freed. ]*/
free(result->hostname);
free(result);
result = NULL;
}
else
{
result->username = NULL;
result->password = NULL;
/* Codes_SRS_HTTP_PROXY_IO_01_006: [ `hostname` and `proxy_hostname`, `username` and `password` shall be copied by calling `mallocAndStrcpy_s`. ]*/
/* Codes_SRS_HTTP_PROXY_IO_01_094: [ `username` and `password` shall be optional. ]*/
if ((http_proxy_io_config->username != NULL) && (mallocAndStrcpy_s(&result->username, http_proxy_io_config->username) != 0))
{
/* Codes_SRS_HTTP_PROXY_IO_01_007: [ If `mallocAndStrcpy_s` fails then `http_proxy_io_create` shall fail and return NULL. ]*/
LogError("Failed to copy the username.");
/* Codes_SRS_HTTP_PROXY_IO_01_008: [ When `http_proxy_io_create` fails, all allocated resources up to that point shall be freed. ]*/
free(result->proxy_hostname);
free(result->hostname);
free(result);
result = NULL;
}
else
{
/* Codes_SRS_HTTP_PROXY_IO_01_006: [ `hostname` and `proxy_hostname`, `username` and `password` shall be copied by calling `mallocAndStrcpy_s`. ]*/
/* Codes_SRS_HTTP_PROXY_IO_01_094: [ `username` and `password` shall be optional. ]*/
if ((http_proxy_io_config->password != NULL) && (mallocAndStrcpy_s(&result->password, http_proxy_io_config->password) != 0))
{
/* Codes_SRS_HTTP_PROXY_IO_01_007: [ If `mallocAndStrcpy_s` fails then `http_proxy_io_create` shall fail and return NULL. ]*/
LogError("Failed to copy the passowrd.");
/* Codes_SRS_HTTP_PROXY_IO_01_008: [ When `http_proxy_io_create` fails, all allocated resources up to that point shall be freed. ]*/
free(result->username);
free(result->proxy_hostname);
free(result->hostname);
free(result);
result = NULL;
}
else
{
/* Codes_SRS_HTTP_PROXY_IO_01_010: [ - `io_interface_description` shall be set to the result of `socketio_get_interface_description`. ]*/
const IO_INTERFACE_DESCRIPTION* underlying_io_interface = socketio_get_interface_description();
if (underlying_io_interface == NULL)
{
/* Codes_SRS_HTTP_PROXY_IO_01_050: [ If `socketio_get_interface_description` fails, `http_proxy_io_create` shall fail and return NULL. ]*/
LogError("Unable to get the socket IO interface description.");
/* Codes_SRS_HTTP_PROXY_IO_01_008: [ When `http_proxy_io_create` fails, all allocated resources up to that point shall be freed. ]*/
free(result->password);
free(result->username);
free(result->proxy_hostname);
free(result->hostname);
free(result);
result = NULL;
}
else
{
SOCKETIO_CONFIG socket_io_config;
/* Codes_SRS_HTTP_PROXY_IO_01_011: [ - `xio_create_parameters` shall be set to a `SOCKETIO_CONFIG*` where `hostname` is set to the `proxy_hostname` member of `io_create_parameters` and `port` is set to the `proxy_port` member of `io_create_parameters`. ]*/
socket_io_config.hostname = http_proxy_io_config->proxy_hostname;
socket_io_config.port = http_proxy_io_config->proxy_port;
socket_io_config.accepted_socket = NULL;
/* Codes_SRS_HTTP_PROXY_IO_01_009: [ `http_proxy_io_create` shall create a new socket IO by calling `xio_create` with the arguments: ]*/
result->underlying_io = xio_create(underlying_io_interface, &socket_io_config);
if (result->underlying_io == NULL)
{
/* Codes_SRS_HTTP_PROXY_IO_01_012: [ If `xio_create` fails, `http_proxy_io_create` shall fail and return NULL. ]*/
LogError("Unable to create the underlying IO.");
/* Codes_SRS_HTTP_PROXY_IO_01_008: [ When `http_proxy_io_create` fails, all allocated resources up to that point shall be freed. ]*/
free(result->password);
free(result->username);
free(result->proxy_hostname);
free(result->hostname);
free(result);
result = NULL;
}
else
{
result->port = http_proxy_io_config->port;
result->proxy_port = http_proxy_io_config->proxy_port;
result->receive_buffer = NULL;
result->receive_buffer_size = 0;
result->http_proxy_io_state = HTTP_PROXY_IO_STATE_CLOSED;
}
}
}
}
}
}
}
}
}
return result;
}
static void http_proxy_io_destroy(CONCRETE_IO_HANDLE http_proxy_io)
{
if (http_proxy_io == NULL)
{
/* Codes_SRS_HTTP_PROXY_IO_01_014: [ If `http_proxy_io` is NULL, `http_proxy_io_destroy` shall do nothing. ]*/
LogError("NULL http_proxy_io.");
}
else
{
HTTP_PROXY_IO_INSTANCE* http_proxy_io_instance = (HTTP_PROXY_IO_INSTANCE*)http_proxy_io;
/* Codes_SRS_HTTP_PROXY_IO_01_013: [ `http_proxy_io_destroy` shall free the HTTP proxy IO instance indicated by `http_proxy_io`. ]*/
if (http_proxy_io_instance->receive_buffer != NULL)
{
free(http_proxy_io_instance->receive_buffer);
}
/* Codes_SRS_HTTP_PROXY_IO_01_016: [ `http_proxy_io_destroy` shall destroy the underlying IO created in `http_proxy_io_create` by calling `xio_destroy`. ]*/
xio_destroy(http_proxy_io_instance->underlying_io);
free(http_proxy_io_instance->hostname);
free(http_proxy_io_instance->proxy_hostname);
free(http_proxy_io_instance->username);
free(http_proxy_io_instance->password);
free(http_proxy_io_instance);
}
}
static void indicate_open_complete_error_and_close(HTTP_PROXY_IO_INSTANCE* http_proxy_io_instance)
{
http_proxy_io_instance->http_proxy_io_state = HTTP_PROXY_IO_STATE_CLOSED;
(void)xio_close(http_proxy_io_instance->underlying_io, NULL, NULL);
http_proxy_io_instance->on_io_open_complete(http_proxy_io_instance->on_io_open_complete_context, IO_OPEN_ERROR);
}
// This callback usage needs to be either verified and commented or integrated into
// the state machine.
static void unchecked_on_send_complete(void* context, IO_SEND_RESULT send_result)
{
(void)context;
(void)send_result;
}
static void on_underlying_io_open_complete(void* context, IO_OPEN_RESULT open_result)
{
if (context == NULL)
{
/* Codes_SRS_HTTP_PROXY_IO_01_081: [ `on_underlying_io_open_complete` called with NULL context shall do nothing. ]*/
LogError("NULL context in on_underlying_io_open_complete");
}
else
{
HTTP_PROXY_IO_INSTANCE* http_proxy_io_instance = (HTTP_PROXY_IO_INSTANCE*)context;
switch (http_proxy_io_instance->http_proxy_io_state)
{
default:
LogError("on_underlying_io_open_complete called in an unexpected state.");
break;
case HTTP_PROXY_IO_STATE_CLOSING:
case HTTP_PROXY_IO_STATE_OPEN:
/* Codes_SRS_HTTP_PROXY_IO_01_077: [ When `on_underlying_io_open_complete` is called in after OPEN has completed, the `on_io_error` callback shall be triggered passing the `on_io_error_context` argument as `context`. ]*/
http_proxy_io_instance->on_io_error(http_proxy_io_instance->on_io_error_context);
break;
case HTTP_PROXY_IO_STATE_WAITING_FOR_CONNECT_RESPONSE:
/* Codes_SRS_HTTP_PROXY_IO_01_076: [ When `on_underlying_io_open_complete` is called while waiting for the CONNECT reply, the `on_open_complete` callback shall be triggered with `IO_OPEN_ERROR`, passing also the `on_open_complete_context` argument as `context`. ]*/
LogError("Open complete called again by underlying IO.");
indicate_open_complete_error_and_close(http_proxy_io_instance);
break;
case HTTP_PROXY_IO_STATE_OPENING_UNDERLYING_IO:
switch (open_result)
{
default:
case IO_OPEN_ERROR:
/* Codes_SRS_HTTP_PROXY_IO_01_078: [ When `on_underlying_io_open_complete` is called with `IO_OPEN_ERROR`, the `on_open_complete` callback shall be triggered with `IO_OPEN_ERROR`, passing also the `on_open_complete_context` argument as `context`. ]*/
LogError("Underlying IO open failed");
indicate_open_complete_error_and_close(http_proxy_io_instance);
break;
case IO_OPEN_CANCELLED:
/* Codes_SRS_HTTP_PROXY_IO_01_079: [ When `on_underlying_io_open_complete` is called with `IO_OPEN_CANCELLED`, the `on_open_complete` callback shall be triggered with `IO_OPEN_CANCELLED`, passing also the `on_open_complete_context` argument as `context`. ]*/
LogError("Underlying IO open failed");
http_proxy_io_instance->http_proxy_io_state = HTTP_PROXY_IO_STATE_CLOSED;
(void)xio_close(http_proxy_io_instance->underlying_io, NULL, NULL);
http_proxy_io_instance->on_io_open_complete(http_proxy_io_instance->on_io_open_complete_context, IO_OPEN_CANCELLED);
break;
case IO_OPEN_OK:
{
STRING_HANDLE encoded_auth_string;
/* Codes_SRS_HTTP_PROXY_IO_01_057: [ When `on_underlying_io_open_complete` is called, the `http_proxy_io` shall send the CONNECT request constructed per RFC 2817: ]*/
http_proxy_io_instance->http_proxy_io_state = HTTP_PROXY_IO_STATE_WAITING_FOR_CONNECT_RESPONSE;
if (http_proxy_io_instance->username != NULL)
{
char* plain_auth_string_bytes;
/* Codes_SRS_HTTP_PROXY_IO_01_060: [ - The value of `Proxy-Authorization` shall be the constructed according to RFC 2617. ]*/
int plain_auth_string_length = (int)(strlen(http_proxy_io_instance->username)+1);
if (http_proxy_io_instance->password != NULL)
{
plain_auth_string_length += (int)strlen(http_proxy_io_instance->password);
}
if (plain_auth_string_length < 0)
{
/* Codes_SRS_HTTP_PROXY_IO_01_062: [ If any failure is encountered while constructing the request, the `on_open_complete` callback shall be triggered with `IO_OPEN_ERROR`, passing also the `on_open_complete_context` argument as `context`. ]*/
encoded_auth_string = NULL;
indicate_open_complete_error_and_close(http_proxy_io_instance);
}
else
{
plain_auth_string_bytes = (char*)malloc(plain_auth_string_length + 1);
if (plain_auth_string_bytes == NULL)
{
/* Codes_SRS_HTTP_PROXY_IO_01_062: [ If any failure is encountered while constructing the request, the `on_open_complete` callback shall be triggered with `IO_OPEN_ERROR`, passing also the `on_open_complete_context` argument as `context`. ]*/
encoded_auth_string = NULL;
indicate_open_complete_error_and_close(http_proxy_io_instance);
}
else
{
/* Codes_SRS_HTTP_PROXY_IO_01_091: [ To receive authorization, the client sends the userid and password, separated by a single colon (":") character, within a base64 [7] encoded string in the credentials. ]*/
/* Codes_SRS_HTTP_PROXY_IO_01_092: [ A client MAY preemptively send the corresponding Authorization header with requests for resources in that space without receipt of another challenge from the server. ]*/
/* Codes_SRS_HTTP_PROXY_IO_01_093: [ Userids might be case sensitive. ]*/
if (sprintf(plain_auth_string_bytes, "%s:%s", http_proxy_io_instance->username, (http_proxy_io_instance->password == NULL) ? "" : http_proxy_io_instance->password) < 0)
{
/* Codes_SRS_HTTP_PROXY_IO_01_062: [ If any failure is encountered while constructing the request, the `on_open_complete` callback shall be triggered with `IO_OPEN_ERROR`, passing also the `on_open_complete_context` argument as `context`. ]*/
encoded_auth_string = NULL;
indicate_open_complete_error_and_close(http_proxy_io_instance);
}
else
{
/* Codes_SRS_HTTP_PROXY_IO_01_061: [ Encoding to Base64 shall be done by calling `Base64_Encode_Bytes`. ]*/
encoded_auth_string = Base64_Encode_Bytes((const unsigned char*)plain_auth_string_bytes, plain_auth_string_length);
if (encoded_auth_string == NULL)
{
/* Codes_SRS_HTTP_PROXY_IO_01_062: [ If any failure is encountered while constructing the request, the `on_open_complete` callback shall be triggered with `IO_OPEN_ERROR`, passing also the `on_open_complete_context` argument as `context`. ]*/
LogError("Cannot Base64 encode auth string");
indicate_open_complete_error_and_close(http_proxy_io_instance);
}
}
free(plain_auth_string_bytes);
}
}
}
else
{
encoded_auth_string = NULL;
}
if ((http_proxy_io_instance->username != NULL) &&
(encoded_auth_string == NULL))
{
LogError("Cannot create authorization header");
}
else
{
int connect_request_length;
const char* auth_string_payload;
/* Codes_SRS_HTTP_PROXY_IO_01_075: [ The Request-URI portion of the Request-Line is always an 'authority' as defined by URI Generic Syntax [2], which is to say the host name and port number destination of the requested connection separated by a colon: ]*/
const char request_format[] = "CONNECT %s:%d HTTP/1.1\r\nHost:%s:%d%s%s\r\n\r\n";
const char proxy_basic[] = "\r\nProxy-authorization: Basic ";
if (http_proxy_io_instance->username != NULL)
{
auth_string_payload = STRING_c_str(encoded_auth_string);
}
else
{
auth_string_payload = "";
}
/* Codes_SRS_HTTP_PROXY_IO_01_059: [ - If `username` and `password` have been specified in the arguments passed to `http_proxy_io_create`, then the header `Proxy-Authorization` shall be added to the request. ]*/
connect_request_length = (int)(strlen(request_format)+(strlen(http_proxy_io_instance->hostname)*2)+strlen(auth_string_payload)+10);
if (http_proxy_io_instance->username != NULL)
{
connect_request_length += (int)strlen(proxy_basic);
}
if (connect_request_length < 0)
{
/* Codes_SRS_HTTP_PROXY_IO_01_062: [ If any failure is encountered while constructing the request, the `on_open_complete` callback shall be triggered with `IO_OPEN_ERROR`, passing also the `on_open_complete_context` argument as `context`. ]*/
LogError("Cannot encode the CONNECT request");
indicate_open_complete_error_and_close(http_proxy_io_instance);
}
else
{
char* connect_request = (char*)malloc(connect_request_length + 1);
if (connect_request == NULL)
{
/* Codes_SRS_HTTP_PROXY_IO_01_062: [ If any failure is encountered while constructing the request, the `on_open_complete` callback shall be triggered with `IO_OPEN_ERROR`, passing also the `on_open_complete_context` argument as `context`. ]*/
LogError("Cannot allocate memory for CONNECT request");
indicate_open_complete_error_and_close(http_proxy_io_instance);
}
else
{
/* Codes_SRS_HTTP_PROXY_IO_01_059: [ - If `username` and `password` have been specified in the arguments passed to `http_proxy_io_create`, then the header `Proxy-Authorization` shall be added to the request. ]*/
connect_request_length = sprintf(connect_request, request_format,
http_proxy_io_instance->hostname,
http_proxy_io_instance->port,
http_proxy_io_instance->hostname,
http_proxy_io_instance->port,
(http_proxy_io_instance->username != NULL) ? proxy_basic : "",
auth_string_payload);
if (connect_request_length < 0)
{
/* Codes_SRS_HTTP_PROXY_IO_01_062: [ If any failure is encountered while constructing the request, the `on_open_complete` callback shall be triggered with `IO_OPEN_ERROR`, passing also the `on_open_complete_context` argument as `context`. ]*/
LogError("Cannot encode the CONNECT request");
indicate_open_complete_error_and_close(http_proxy_io_instance);
}
else
{
/* Codes_SRS_HTTP_PROXY_IO_01_063: [ The request shall be sent by calling `xio_send` and passing NULL as `on_send_complete` callback. ]*/
if (xio_send(http_proxy_io_instance->underlying_io, connect_request, connect_request_length, unchecked_on_send_complete, NULL) != 0)
{
/* Codes_SRS_HTTP_PROXY_IO_01_064: [ If `xio_send` fails, the `on_open_complete` callback shall be triggered with `IO_OPEN_ERROR`, passing also the `on_open_complete_context` argument as `context`. ]*/
LogError("Could not send CONNECT request");
indicate_open_complete_error_and_close(http_proxy_io_instance);
}
}
free(connect_request);
}
}
}
if (encoded_auth_string != NULL)
{
STRING_delete(encoded_auth_string);
}
break;
}
}
break;
}
}
}
static void on_underlying_io_error(void* context)
{
if (context == NULL)
{
/* Codes_SRS_HTTP_PROXY_IO_01_088: [ `on_underlying_io_error` called with NULL context shall do nothing. ]*/
LogError("NULL context in on_underlying_io_error");
}
else
{
HTTP_PROXY_IO_INSTANCE* http_proxy_io_instance = (HTTP_PROXY_IO_INSTANCE*)context;
switch (http_proxy_io_instance->http_proxy_io_state)
{
default:
LogError("on_underlying_io_error in invalid state");
break;
case HTTP_PROXY_IO_STATE_OPENING_UNDERLYING_IO:
case HTTP_PROXY_IO_STATE_WAITING_FOR_CONNECT_RESPONSE:
/* Codes_SRS_HTTP_PROXY_IO_01_087: [ If the `on_underlying_io_error` callback is called while OPENING, the `on_open_complete` callback shall be triggered with `IO_OPEN_ERROR`, passing also the `on_open_complete_context` argument as `context`. ]*/
indicate_open_complete_error_and_close(http_proxy_io_instance);
break;
case HTTP_PROXY_IO_STATE_OPEN:
/* Codes_SRS_HTTP_PROXY_IO_01_089: [ If the `on_underlying_io_error` callback is called while the IO is OPEN, the `on_io_error` callback shall be called with the `on_io_error_context` argument as `context`. ]*/
http_proxy_io_instance->http_proxy_io_state = HTTP_PROXY_IO_STATE_ERROR;
http_proxy_io_instance->on_io_error(http_proxy_io_instance->on_io_error_context);
break;
}
}
}
static void on_underlying_io_close_complete(void* context)
{
if (context == NULL)
{
/* Cdoes_SRS_HTTP_PROXY_IO_01_084: [ `on_underlying_io_close_complete` called with NULL context shall do nothing. ]*/
LogError("NULL context in on_underlying_io_open_complete");
}
else
{
HTTP_PROXY_IO_INSTANCE* http_proxy_io_instance = (HTTP_PROXY_IO_INSTANCE*)context;
switch (http_proxy_io_instance->http_proxy_io_state)
{
default:
LogError("on_underlying_io_close_complete called in an invalid state");
break;
case HTTP_PROXY_IO_STATE_CLOSING:
http_proxy_io_instance->http_proxy_io_state = HTTP_PROXY_IO_STATE_CLOSED;
/* Codes_SRS_HTTP_PROXY_IO_01_086: [ If the `on_io_close_complete` callback passed to `http_proxy_io_close` was NULL, no callback shall be triggered. ]*/
if (http_proxy_io_instance->on_io_close_complete != NULL)
{
/* Codes_SRS_HTTP_PROXY_IO_01_083: [ `on_underlying_io_close_complete` while CLOSING shall call the `on_io_close_complete` callback, passing to it the `on_io_close_complete_context` as `context` argument. ]*/
http_proxy_io_instance->on_io_close_complete(http_proxy_io_instance->on_io_close_complete_context);
}
break;
}
}
}
/*the following function does the same as sscanf(pos2, "%d", &sec)*/
/*this function only exists because some of platforms do not have sscanf. */
static int ParseStringToDecimal(const char *src, int* dst)
{
int result;
char* next;
(*dst) = (int)strtol(src, &next, 0);
if ((src == next) || ((((*dst) == INT_MAX) || ((*dst) == INT_MIN)) && (errno != 0)))
{
result = __LINE__;
}
else
{
result = 0;
}
return result;
}
/*the following function does the same as sscanf(buf, "HTTP/%*d.%*d %d %*[^\r\n]", &ret) */
/*this function only exists because some of platforms do not have sscanf. This is not a full implementation; it only works with well-defined HTTP response. */
static int ParseHttpResponse(const char* src, int* dst)
{
int result;
static const char HTTPPrefix[] = "HTTP/";
bool fail;
const char* runPrefix;
if ((src == NULL) || (dst == NULL))
{
result = __LINE__;
}
else
{
fail = false;
runPrefix = HTTPPrefix;
while ((*runPrefix) != '\0')
{
if ((*runPrefix) != (*src))
{
fail = true;
break;
}
src++;
runPrefix++;
}
if (!fail)
{
while ((*src) != '.')
{
if ((*src) == '\0')
{
fail = true;
break;
}
src++;
}
}
if (!fail)
{
while ((*src) != ' ')
{
if ((*src) == '\0')
{
fail = true;
break;
}
src++;
}
}
if (fail)
{
result = __LINE__;
}
else
{
if (ParseStringToDecimal(src, dst) != 0)
{
result = __LINE__;
}
else
{
result = 0;
}
}
}
return result;
}
static void on_underlying_io_bytes_received(void* context, const unsigned char* buffer, size_t size)
{
if (context == NULL)
{
/* Codes_SRS_HTTP_PROXY_IO_01_082: [ `on_underlying_io_bytes_received` called with NULL context shall do nothing. ]*/
LogError("NULL context in on_underlying_io_bytes_received");
}
else
{
HTTP_PROXY_IO_INSTANCE* http_proxy_io_instance = (HTTP_PROXY_IO_INSTANCE*)context;
switch (http_proxy_io_instance->http_proxy_io_state)
{
default:
case HTTP_PROXY_IO_STATE_CLOSING:
LogError("Bytes received in invalid state");
break;
case HTTP_PROXY_IO_STATE_OPENING_UNDERLYING_IO:
/* Codes_SRS_HTTP_PROXY_IO_01_080: [ If `on_underlying_io_bytes_received` is called while the underlying IO is being opened, the `on_open_complete` callback shall be triggered with `IO_OPEN_ERROR`, passing also the `on_open_complete_context` argument as `context`. ]*/
LogError("Bytes received while opening underlying IO");
indicate_open_complete_error_and_close(http_proxy_io_instance);
break;
case HTTP_PROXY_IO_STATE_WAITING_FOR_CONNECT_RESPONSE:
{
/* Codes_SRS_HTTP_PROXY_IO_01_065: [ When bytes are received and the response to the CONNECT request was not yet received, the bytes shall be accumulated until a double new-line is detected. ]*/
unsigned char* new_receive_buffer = (unsigned char*)realloc(http_proxy_io_instance->receive_buffer, http_proxy_io_instance->receive_buffer_size + size + 1);
if (new_receive_buffer == NULL)
{
/* Codes_SRS_HTTP_PROXY_IO_01_067: [ If allocating memory for the buffered bytes fails, the `on_open_complete` callback shall be triggered with `IO_OPEN_ERROR`, passing also the `on_open_complete_context` argument as `context`. ]*/
LogError("Cannot allocate memory for received data");
indicate_open_complete_error_and_close(http_proxy_io_instance);
}
else
{
http_proxy_io_instance->receive_buffer = new_receive_buffer;
memcpy(http_proxy_io_instance->receive_buffer + http_proxy_io_instance->receive_buffer_size, buffer, size);
http_proxy_io_instance->receive_buffer_size += size;
}
if (http_proxy_io_instance->receive_buffer_size >= 4)
{
const char* request_end_ptr;
http_proxy_io_instance->receive_buffer[http_proxy_io_instance->receive_buffer_size] = 0;
/* Codes_SRS_HTTP_PROXY_IO_01_066: [ When a double new-line is detected the response shall be parsed in order to extract the status code. ]*/
if ((http_proxy_io_instance->receive_buffer_size >= 4) &&
((request_end_ptr = strstr((const char*)http_proxy_io_instance->receive_buffer, "\r\n\r\n")) != NULL))
{
int status_code;
/* This part should really be done with the HTTPAPI, but that has to be done as a separate step
as the HTTPAPI has to expose somehow the underlying IO and currently this would be a too big of a change. */
if (ParseHttpResponse((const char*)http_proxy_io_instance->receive_buffer, &status_code) != 0)
{
/* Codes_SRS_HTTP_PROXY_IO_01_068: [ If parsing the CONNECT response fails, the `on_open_complete` callback shall be triggered with `IO_OPEN_ERROR`, passing also the `on_open_complete_context` argument as `context`. ]*/
LogError("Cannot decode HTTP response");
indicate_open_complete_error_and_close(http_proxy_io_instance);
}
/* Codes_SRS_HTTP_PROXY_IO_01_069: [ Any successful (2xx) response to a CONNECT request indicates that the proxy has established a connection to the requested host and port, and has switched to tunneling the current connection to that server connection. ]*/
/* Codes_SRS_HTTP_PROXY_IO_01_090: [ Any successful (2xx) response to a CONNECT request indicates that the proxy has established a connection to the requested host and port, and has switched to tunneling the current connection to that server connection. ]*/
else if ((status_code < 200) || (status_code > 299))
{
/* Codes_SRS_HTTP_PROXY_IO_01_071: [ If the status code is not successful, the `on_open_complete` callback shall be triggered with `IO_OPEN_ERROR`, passing also the `on_open_complete_context` argument as `context`. ]*/
LogError("Bad status (%d) received in CONNECT response", status_code);
indicate_open_complete_error_and_close(http_proxy_io_instance);
}
else
{
size_t length_remaining = http_proxy_io_instance->receive_buffer + http_proxy_io_instance->receive_buffer_size - ((const unsigned char *)request_end_ptr + 4);
/* Codes_SRS_HTTP_PROXY_IO_01_073: [ Once a success status code was parsed, the IO shall be OPEN. ]*/
http_proxy_io_instance->http_proxy_io_state = HTTP_PROXY_IO_STATE_OPEN;
/* Codes_SRS_HTTP_PROXY_IO_01_070: [ When a success status code is parsed, the `on_open_complete` callback shall be triggered with `IO_OPEN_OK`, passing also the `on_open_complete_context` argument as `context`. ]*/
http_proxy_io_instance->on_io_open_complete(http_proxy_io_instance->on_io_open_complete_context, IO_OPEN_OK);
if (length_remaining > 0)
{
/* Codes_SRS_HTTP_PROXY_IO_01_072: [ Any bytes that are extra (not consumed by the CONNECT response), shall be indicated as received by calling the `on_bytes_received` callback and passing the `on_bytes_received_context` as context argument. ]*/
http_proxy_io_instance->on_bytes_received(http_proxy_io_instance->on_bytes_received_context, (const unsigned char*)request_end_ptr + 4, length_remaining);
}
}
}
}
break;
}
case HTTP_PROXY_IO_STATE_OPEN:
/* Codes_SRS_HTTP_PROXY_IO_01_074: [ If `on_underlying_io_bytes_received` is called while OPEN, all bytes shall be indicated as received by calling the `on_bytes_received` callback and passing the `on_bytes_received_context` as context argument. ]*/
http_proxy_io_instance->on_bytes_received(http_proxy_io_instance->on_bytes_received_context, buffer, size);
break;
}
}
}
static int http_proxy_io_open(CONCRETE_IO_HANDLE http_proxy_io, ON_IO_OPEN_COMPLETE on_io_open_complete, void* on_io_open_complete_context, ON_BYTES_RECEIVED on_bytes_received, void* on_bytes_received_context, ON_IO_ERROR on_io_error, void* on_io_error_context)
{
int result;
/* Codes_SRS_HTTP_PROXY_IO_01_051: [ The arguments `on_io_open_complete_context`, `on_bytes_received_context` and `on_io_error_context` shall be allowed to be NULL. ]*/
/* Codes_SRS_HTTP_PROXY_IO_01_018: [ If any of the arguments `http_proxy_io`, `on_io_open_complete`, `on_bytes_received` or `on_io_error` are NULL then `http_proxy_io_open` shall return a non-zero value. ]*/
if ((http_proxy_io == NULL) ||
(on_io_open_complete == NULL) ||
(on_bytes_received == NULL) ||
(on_io_error == NULL))
{
LogError("Bad arguments: http_proxy_io = %p, on_io_open_complete = %p, on_bytes_received = %p, on_io_error_context = %p.",
http_proxy_io,
on_io_open_complete,
on_bytes_received,
on_io_error);
result = __LINE__;
}
else
{
HTTP_PROXY_IO_INSTANCE* http_proxy_io_instance = (HTTP_PROXY_IO_INSTANCE*)http_proxy_io;
if (http_proxy_io_instance->http_proxy_io_state != HTTP_PROXY_IO_STATE_CLOSED)
{
LogError("Invalid tlsio_state. Expected state is HTTP_PROXY_IO_STATE_CLOSED.");
result = __LINE__;
}
else
{
http_proxy_io_instance->on_bytes_received = on_bytes_received;
http_proxy_io_instance->on_bytes_received_context = on_bytes_received_context;
http_proxy_io_instance->on_io_error = on_io_error;
http_proxy_io_instance->on_io_error_context = on_io_error_context;
http_proxy_io_instance->on_io_open_complete = on_io_open_complete;
http_proxy_io_instance->on_io_open_complete_context = on_io_open_complete_context;
http_proxy_io_instance->http_proxy_io_state = HTTP_PROXY_IO_STATE_OPENING_UNDERLYING_IO;
/* Codes_SRS_HTTP_PROXY_IO_01_019: [ `http_proxy_io_open` shall open the underlying IO by calling `xio_open` on the underlying IO handle created in `http_proxy_io_create`, while passing to it the callbacks `on_underlying_io_open_complete`, `on_underlying_io_bytes_received` and `on_underlying_io_error`. ]*/
if (xio_open(http_proxy_io_instance->underlying_io, on_underlying_io_open_complete, http_proxy_io_instance, on_underlying_io_bytes_received, http_proxy_io_instance, on_underlying_io_error, http_proxy_io_instance) != 0)
{
/* Codes_SRS_HTTP_PROXY_IO_01_020: [ If `xio_open` fails, then `http_proxy_io_open` shall return a non-zero value. ]*/
http_proxy_io_instance->http_proxy_io_state = HTTP_PROXY_IO_STATE_CLOSED;
LogError("Cannot open the underlying IO.");
result = __LINE__;
}
else
{
/* Codes_SRS_HTTP_PROXY_IO_01_017: [ `http_proxy_io_open` shall open the HTTP proxy IO and on success it shall return 0. ]*/
result = 0;
}
}
}
return result;
}
static int http_proxy_io_close(CONCRETE_IO_HANDLE http_proxy_io, ON_IO_CLOSE_COMPLETE on_io_close_complete, void* on_io_close_complete_context)
{
int result = 0;
/* Codes_SRS_HTTP_PROXY_IO_01_052: [ `on_io_close_complete_context` shall be allowed to be NULL. ]*/
/* Codes_SRS_HTTP_PROXY_IO_01_028: [ `on_io_close_complete` shall be allowed to be NULL. ]*/
if (http_proxy_io == NULL)
{
/* Codes_SRS_HTTP_PROXY_IO_01_023: [ If the argument `http_proxy_io` is NULL, `http_proxy_io_close` shall fail and return a non-zero value. ]*/
result = __LINE__;
LogError("NULL http_proxy_io.");
}
else
{
HTTP_PROXY_IO_INSTANCE* http_proxy_io_instance = (HTTP_PROXY_IO_INSTANCE*)http_proxy_io;
/* Codes_SRS_HTTP_PROXY_IO_01_027: [ If `http_proxy_io_close` is called when not open, `http_proxy_io_close` shall fail and return a non-zero value. ]*/
if ((http_proxy_io_instance->http_proxy_io_state == HTTP_PROXY_IO_STATE_CLOSED) ||
/* Codes_SRS_HTTP_PROXY_IO_01_054: [ `http_proxy_io_close` while OPENING shall fail and return a non-zero value. ]*/
(http_proxy_io_instance->http_proxy_io_state == HTTP_PROXY_IO_STATE_CLOSING))
{
result = __LINE__;
LogError("Invalid tlsio_state. Expected state is HTTP_PROXY_IO_STATE_OPEN.");
}
else if ((http_proxy_io_instance->http_proxy_io_state == HTTP_PROXY_IO_STATE_OPENING_UNDERLYING_IO) ||
(http_proxy_io_instance->http_proxy_io_state == HTTP_PROXY_IO_STATE_WAITING_FOR_CONNECT_RESPONSE))
{
/* Codes_SRS_HTTP_PROXY_IO_01_053: [ `http_proxy_io_close` while OPENING shall trigger the `on_io_open_complete` callback with `IO_OPEN_CANCELLED`. ]*/
http_proxy_io_instance->http_proxy_io_state = HTTP_PROXY_IO_STATE_CLOSED;
(void)xio_close(http_proxy_io_instance->underlying_io, NULL, NULL);
http_proxy_io_instance->on_io_open_complete(http_proxy_io_instance->on_io_open_complete_context, IO_OPEN_CANCELLED);
/* Codes_SRS_HTTP_PROXY_IO_01_022: [ `http_proxy_io_close` shall close the HTTP proxy IO and on success it shall return 0. ]*/
result = 0;
}
else
{
HTTP_PROXY_IO_STATE previous_state = http_proxy_io_instance->http_proxy_io_state;
http_proxy_io_instance->http_proxy_io_state = HTTP_PROXY_IO_STATE_CLOSING;
/* Codes_SRS_HTTP_PROXY_IO_01_026: [ The `on_io_close_complete` and `on_io_close_complete_context` arguments shall be saved for later use. ]*/
http_proxy_io_instance->on_io_close_complete = on_io_close_complete;
http_proxy_io_instance->on_io_close_complete_context = on_io_close_complete_context;
/* Codes_SRS_HTTP_PROXY_IO_01_024: [ `http_proxy_io_close` shall close the underlying IO by calling `xio_close` on the IO handle create in `http_proxy_io_create`, while passing to it the `on_underlying_io_close_complete` callback. ]*/
if (xio_close(http_proxy_io_instance->underlying_io, on_underlying_io_close_complete, http_proxy_io_instance) != 0)
{
/* Codes_SRS_HTTP_PROXY_IO_01_025: [ If `xio_close` fails, `http_proxy_io_close` shall fail and return a non-zero value. ]*/
result = __LINE__;
http_proxy_io_instance->http_proxy_io_state = previous_state;
LogError("Cannot close underlying IO.");
}
else
{
/* Codes_SRS_HTTP_PROXY_IO_01_022: [ `http_proxy_io_close` shall close the HTTP proxy IO and on success it shall return 0. ]*/
result = 0;
}
}
}
return result;
}
static int http_proxy_io_send(CONCRETE_IO_HANDLE http_proxy_io, const void* buffer, size_t size, ON_SEND_COMPLETE on_send_complete, void* on_send_complete_context)
{
int result;
/* Codes_SRS_HTTP_PROXY_IO_01_032: [ `on_send_complete` shall be allowed to be NULL. ]*/
/* Codes_SRS_HTTP_PROXY_IO_01_030: [ If any of the arguments `http_proxy_io` or `buffer` is NULL, `http_proxy_io_send` shall fail and return a non-zero value. ]*/
if ((http_proxy_io == NULL) ||
(buffer == NULL) ||
/* Codes_SRS_HTTP_PROXY_IO_01_031: [ If `size` is 0, `http_proxy_io_send` shall fail and return a non-zero value. ]*/
(size == 0))
{
result = __LINE__;
LogError("Bad arguments: http_proxy_io = %p, buffer = %p.",
http_proxy_io, buffer);
}
else
{
HTTP_PROXY_IO_INSTANCE* http_proxy_io_instance = (HTTP_PROXY_IO_INSTANCE*)http_proxy_io;
/* Codes_SRS_HTTP_PROXY_IO_01_034: [ If `http_proxy_io_send` is called when the IO is not open, `http_proxy_io_send` shall fail and return a non-zero value. ]*/
/* Codes_SRS_HTTP_PROXY_IO_01_035: [ If the IO is in an error state (an error was reported through the `on_io_error` callback), `http_proxy_io_send` shall fail and return a non-zero value. ]*/
if (http_proxy_io_instance->http_proxy_io_state != HTTP_PROXY_IO_STATE_OPEN)
{
result = __LINE__;
LogError("Invalid HTTP proxy IO state. Expected state is HTTP_PROXY_IO_STATE_OPEN.");
}
else
{
/* Codes_SRS_HTTP_PROXY_IO_01_033: [ `http_proxy_io_send` shall send the bytes by calling `xio_send` on the underlying IO created in `http_proxy_io_create` and passing `buffer` and `size` as arguments. ]*/
if (xio_send(http_proxy_io_instance->underlying_io, buffer, size, on_send_complete, on_send_complete_context) != 0)
{
/* Codes_SRS_HTTP_PROXY_IO_01_055: [ If `xio_send` fails, `http_proxy_io_send` shall fail and return a non-zero value. ]*/
result = __LINE__;
LogError("Underlying xio_send failed.");
}
else
{
/* Codes_SRS_HTTP_PROXY_IO_01_029: [ `http_proxy_io_send` shall send the `size` bytes pointed to by `buffer` and on success it shall return 0. ]*/
result = 0;
}
}
}
return result;
}
static void http_proxy_io_dowork(CONCRETE_IO_HANDLE http_proxy_io)
{
if (http_proxy_io == NULL)
{
/* Codes_SRS_HTTP_PROXY_IO_01_038: [ If the `http_proxy_io` argument is NULL, `http_proxy_io_dowork` shall do nothing. ]*/
LogError("NULL http_proxy_io.");
}
else
{
HTTP_PROXY_IO_INSTANCE* http_proxy_io_instance = (HTTP_PROXY_IO_INSTANCE*)http_proxy_io;
if (http_proxy_io_instance->http_proxy_io_state != HTTP_PROXY_IO_STATE_CLOSED)
{
/* Codes_SRS_HTTP_PROXY_IO_01_037: [ `http_proxy_io_dowork` shall call `xio_dowork` on the underlying IO created in `http_proxy_io_create`. ]*/
xio_dowork(http_proxy_io_instance->underlying_io);
}
}
}
static int http_proxy_io_set_option(CONCRETE_IO_HANDLE http_proxy_io, const char* option_name, const void* value)
{
int result;
if ((http_proxy_io == NULL) || (option_name == NULL))
{
/* Codes_SRS_HTTP_PROXY_IO_01_040: [ If any of the arguments `http_proxy_io` or `option_name` is NULL, `http_proxy_io_set_option` shall return a non-zero value. ]*/
LogError("Bad arguments: http_proxy_io = %p, option_name = %p",
http_proxy_io, option_name);
result = __LINE__;
}
else
{
HTTP_PROXY_IO_INSTANCE* http_proxy_io_instance = (HTTP_PROXY_IO_INSTANCE*)http_proxy_io;
/* Codes_SRS_HTTP_PROXY_IO_01_045: [ None. ]*/
/* Codes_SRS_HTTP_PROXY_IO_01_043: [ If the `option_name` argument indicates an option that is not handled by `http_proxy_io_set_option`, then `xio_setoption` shall be called on the underlying IO created in `http_proxy_io_create`, passing the option name and value to it. ]*/
/* Codes_SRS_HTTP_PROXY_IO_01_056: [ The `value` argument shall be allowed to be NULL. ]*/
if (xio_setoption(http_proxy_io_instance->underlying_io, option_name, value) != 0)
{
/* Codes_SRS_HTTP_PROXY_IO_01_044: [ if `xio_setoption` fails, `http_proxy_io_set_option` shall return a non-zero value. ]*/
LogError("Unrecognized option");
result = __LINE__;
}
else
{
/* Codes_SRS_HTTP_PROXY_IO_01_042: [ If the option was handled by `http_proxy_io_set_option` or the underlying IO, then `http_proxy_io_set_option` shall return 0. ]*/
result = 0;
}
}
return result;
}
static OPTIONHANDLER_HANDLE http_proxy_io_retrieve_options(CONCRETE_IO_HANDLE http_proxy_io)
{
OPTIONHANDLER_HANDLE result;
if (http_proxy_io == NULL)
{
/* Codes_SRS_HTTP_PROXY_IO_01_047: [ If the parameter `http_proxy_io` is NULL then `http_proxy_io_retrieve_options` shall fail and return NULL. ]*/
LogError("invalid parameter detected: CONCRETE_IO_HANDLE handle=%p", http_proxy_io);
result = NULL;
}
else
{
HTTP_PROXY_IO_INSTANCE* http_proxy_io_instance = (HTTP_PROXY_IO_INSTANCE*)http_proxy_io;
/* Codes_SRS_HTTP_PROXY_IO_01_046: [ `http_proxy_io_retrieve_options` shall return an `OPTIONHANDLER_HANDLE` obtained by calling `xio_retrieveoptions` on the underlying IO created in `http_proxy_io_create`. ]*/
result = xio_retrieveoptions(http_proxy_io_instance->underlying_io);
if (result == NULL)
{
/* Codes_SRS_HTTP_PROXY_IO_01_048: [ If `xio_retrieveoptions` fails, `http_proxy_io_retrieve_options` shall return NULL. ]*/
LogError("unable to create option handler");
}
}
return result;
}
static const IO_INTERFACE_DESCRIPTION http_proxy_io_interface_description =
{
http_proxy_io_retrieve_options,
http_proxy_io_create,
http_proxy_io_destroy,
http_proxy_io_open,
http_proxy_io_close,
http_proxy_io_send,
http_proxy_io_dowork,
http_proxy_io_set_option
};
const IO_INTERFACE_DESCRIPTION* http_proxy_io_get_interface_description(void)
{
/* Codes_SRS_HTTP_PROXY_IO_01_049: [ `http_proxy_io_get_interface_description` shall return a pointer to an `IO_INTERFACE_DESCRIPTION` structure that contains pointers to the functions: `http_proxy_io_retrieve_options`, `http_proxy_io_retrieve_create`, `http_proxy_io_destroy`, `http_proxy_io_open`, `http_proxy_io_close`, `http_proxy_io_send` and `http_proxy_io_dowork`. ]*/
return &http_proxy_io_interface_description;
}