// Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. #include #include #include #include #include #include #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/azure_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 = Azure_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; }