/* ****************************************************************************** * SOURCE FILE * * Document no: @(#) $Name: VXWORKS_ITER40_2019102207 $ $RCSfile: ipike_http_fetcher.c,v $ $Revision: 1.4.10.1 $ * $Source: /home/interpeak/CVSRoot/ipike/src/ipike_http_fetcher.c,v $ * $Author: ulf $ $Date: 2011-11-16 10:33:33 $ * $State: Exp $ $Locker: $ * * INTERPEAK_COPYRIGHT_STRING * Design and implementation by Erik Dahlberg ****************************************************************************** */ /* **************************************************************************** * 1 DESCRIPTION **************************************************************************** */ /* Provides functionality to fetch files (certificates) remotely using HTTP */ /* **************************************************************************** * 2 CONFIGURATION **************************************************************************** */ #define IPCOM_USE_CLIB_PROTO #include "ipike_config.h" /* **************************************************************************** * 3 INCLUDE FILES **************************************************************************** */ #include "ipike_h.h" #include #include #include #include #include #include #include #include #include "ipike_http_fetcher.h" /* **************************************************************************** * 4 EXTERN PROTOTYPES **************************************************************************** */ /* **************************************************************************** * 5 DEFINES **************************************************************************** */ #define IPIKE_HTTP_REQUEST_FMT_STRING "GET %s HTTP/1.1\r\nhost: %s\r\n\r\n" #define IPIKE_HTTP_REQUEST_BUF_SIZE 256 #define IPIKE_HTTP_RESPONSE_BUF_SIZE_DEFAULT 4096 #define IPIKE_HTTP_RESPONSE_STATUS_OK "HTTP/1.1 200" #define IPIKE_HTTP_RESPONSE_STATUS_OK_LEN 12 #define IPIKE_HTTP_PORT 80 #define IPIKE_HTTP_PREFIX "http://" #define IPIKE_HTTP_PREFIX_LEN 7 /* HTTP socket state */ #define IPIKE_HTTP_SOCK_STATE_CREATED (1 << 0) #define IPIKE_HTTP_SOCK_STATE_CONNECTED (1 << 1) /* **************************************************************************** * 6 TYPES **************************************************************************** */ /* URL */ typedef struct Ipike_http_fetcher_url_st { char *address; char *path; } Ipike_http_fetcher_url; /* HTTP fetcher context */ typedef struct Ipike_http_fetcher_ctx_st { Ipcom_list list; /* Context list */ Ipcom_tmo2 tmo; /* Timeout entry */ Ipike_http_fetcher_callback callback; /* User callback */ void *callback_ctx; /* User callback context */ Ip_fd fd; /* Socket filedescriptor */ Ipike_http_fetcher_url *url; /* URL */ Ip_u32 state; /* Socket state */ } Ipike_http_fetcher_ctx; /* **************************************************************************** * 7 LOCAL PROTOTYPES **************************************************************************** */ /* **************************************************************************** * 8 DATA **************************************************************************** */ IP_STATIC Ipcom_list ctx_list_head = { .next = &ctx_list_head, .prev = &ctx_list_head, .head = &ctx_list_head, .size = 0 }; /* **************************************************************************** * 9 LOCAL FUNCTIONS **************************************************************************** */ /* *=========================================================================== * Ipike_http_fetcher_url_alloc *=========================================================================== */ IP_STATIC Ipike_http_fetcher_url * ipike_http_fetcher_url_alloc() { Ipike_http_fetcher_url *url; url = ipcom_malloc(sizeof(Ipike_http_fetcher_url)); if ( url ) ipcom_memset(url, 0, sizeof(Ipike_http_fetcher_url)); return url; } /* *=========================================================================== * Ipike_http_fetcher_url_destroy *=========================================================================== */ IP_STATIC void ipike_http_fetcher_url_destroy(Ipike_http_fetcher_url *url) { ip_assert(url); if ( url->address ) ipcom_free(url->address); if ( url->path ) ipcom_free(url->path); ipcom_free(url); } /* *=========================================================================== * Ipike_http_fetcher_ctx_alloc *=========================================================================== */ IP_STATIC Ipike_http_fetcher_ctx * ipike_http_fetcher_ctx_alloc() { Ipike_http_fetcher_ctx *ctx; ctx = ipcom_malloc(sizeof(Ipike_http_fetcher_ctx)); if ( ctx ) { ipcom_memset(ctx, 0, sizeof(Ipike_http_fetcher_ctx)); ctx->fd = IP_INVALID_SOCKET; } return ctx; } /* *=========================================================================== * ipike_http_fetcher_ctx_destroy *=========================================================================== */ IP_STATIC void ipike_http_fetcher_ctx_destroy(Ipike_http_fetcher_ctx *ctx) { ip_assert(ctx); ipike_tmo2_cancel(&ctx->tmo); if ( ctx->fd != IP_INVALID_SOCKET ) ipcom_socketclose(ctx->fd); if ( ctx->url ) ipike_http_fetcher_url_destroy(ctx->url); ipcom_free(ctx); } /* *=========================================================================== * ipike_http_fetcher_tmo_cb *=========================================================================== */ IP_STATIC Ip_bool ipike_http_fetcher_tmo_cb(Ipike_http_fetcher_ctx *ctx) { if ( ctx->state == IPIKE_HTTP_SOCK_STATE_CREATED ) { IPCOM_LOG1(WARNING, "ipike_http_fetcher_tmo_cb() :: could not connect"\ " to %s", ctx->url->address); } else { IPCOM_LOG1(WARNING, "ipike_http_fetcher_tmo_cb() :: server %s did not"\ " respond", ctx->url->address); } /* Notify failure to user callback */ ctx->callback(IPIKE_FAILED, IP_NULL, 0, ctx->callback_ctx); ipcom_list_remove(&ctx->list); ipike_http_fetcher_ctx_destroy(ctx); return IP_FALSE; } /* *=========================================================================== * ipike_http_fetcher_url_parse_string *=========================================================================== */ IP_STATIC Ipike_http_fetcher_url * ipike_http_fetcher_url_parse_string(const char *url_string) { Ipike_http_fetcher_url *url = IP_NULL; const char *addr_ptr = IP_NULL; const char *path_ptr = IP_NULL; Ip_size_t addr_len = 0; if ( ipcom_strncmp(url_string, IPIKE_HTTP_PREFIX, IPIKE_HTTP_PREFIX_LEN) != 0 ) goto error_out; url = ipike_http_fetcher_url_alloc(); if ( !url ) goto error_out; /* Make a local copy of the string without the url prefix */ addr_ptr = url_string + IPIKE_HTTP_PREFIX_LEN; /* Get the start of the path */ path_ptr = ipcom_strchr(addr_ptr, '/'); /* Make sure we have ha '/' and something after it */ if ( !path_ptr || *path_ptr == '\0' ) goto error_out; url->path = ipcom_strdup( path_ptr ); /* Get length of the address */ addr_len = (Ip_size_t) (path_ptr - addr_ptr); url->address = ipcom_malloc( addr_len + 1 ); if ( !url->address ) goto error_out; ipcom_strncpy( url->address, addr_ptr, addr_len ); url->address[addr_len] = '\0'; return url; error_out: if ( url ) ipike_http_fetcher_url_destroy( url ); return IP_NULL; } /* *=========================================================================== * Ipike_http_fetcher_get_content *=========================================================================== */ IP_STATIC const char * ipike_http_fetcher_get_content( const char *http_response, Ip_size_t http_response_len, Ip_size_t *content_len) { const char *needle = "\r\n\r\n"; const char *ptr; const char *end_ptr; const char *needle_ptr; end_ptr = http_response + http_response_len; needle_ptr = needle; /* Search for the "needle*" */ for (ptr = http_response; ptr < end_ptr; ptr++) { if ( *ptr == *needle_ptr ) needle_ptr++; else needle_ptr = needle; if ( *needle_ptr == '\0' ) { /* Match */ ptr++; *content_len = (Ip_size_t) (end_ptr - ptr); if ( *content_len == 0 ) return IP_NULL; return ptr; } } *content_len = 0; return IP_NULL; } /* *=========================================================================== * ipike_http_fetcher_process_response *=========================================================================== */ IP_STATIC void ipike_http_fetcher_process_response( Ipike_http_fetcher_ctx *ctx ) { char *resp_buf = IP_NULL; const char *content = IP_NULL; Ip_ssize_t recv_len = 0; Ip_size_t content_len = 0; Ip_size_t resp_buf_size = 0; Ipike_error status = IPIKE_FAILED;; ip_assert(ctx); /* Remove the context from the list */ ipcom_list_remove(&ctx->list); resp_buf_size = (Ip_size_t) ipcom_sysvar_get_as_int("ipike.http_response_buf_size", IPIKE_HTTP_RESPONSE_BUF_SIZE_DEFAULT); /* Allocate response buffer */ resp_buf = ipcom_malloc(resp_buf_size); if ( !resp_buf ) { IPCOM_LOG1(CRIT, "ipike_http_fetcher_process_response() :: "\ "failed to allocate response buffer (%lu)", resp_buf_size); goto done; } /* Receive data */ recv_len = ipcom_recv(ctx->fd, resp_buf, resp_buf_size, 0); if ( recv_len < 0 ) { IPCOM_LOG1(ERR, "ipike_http_fetcher_process_response :: receive error \"%s\"", ipcom_strerror(ipcom_errno)); goto done; } /* Were we able to read all data */ if ( (Ip_size_t) recv_len == resp_buf_size ) { IPCOM_LOG1(ERR, "ipike_http_fetcher_process_response() :: "\ "response buffer to small (%lu)\n", resp_buf_size); goto done; } /* The received data must at least contain a HTTP OK code */ if ( recv_len <= IPIKE_HTTP_RESPONSE_STATUS_OK_LEN ) { IPCOM_LOG1(WARNING, "ipike_http_fetcher_process_response() :: "\ "not enough data in http response (%ld)", recv_len); goto done; } IPCOM_LOG1(DEBUG2, "ipike_http_fetcher_process_response() :: "\ "received %ld bytes of http data", recv_len); /* Check for HTTP OK */ if ( ipcom_strncmp( resp_buf, IPIKE_HTTP_RESPONSE_STATUS_OK, IPIKE_HTTP_RESPONSE_STATUS_OK_LEN) != 0 ) { IPCOM_LOG0(WARNING, "ipike_http_fetcher_process_response() :: "\ "no ok code in http response"); goto done; } /* Ignore the rest of the HTTP Header and just get the content */ content = ipike_http_fetcher_get_content( resp_buf, (Ip_size_t) recv_len, &content_len); if ( !content ) { IPCOM_LOG0(WARNING, "ipike_http_fetcher_process_response() :: "\ "no http content in response data"); goto done; } /* All success */ status = IPIKE_SUCCESS;; done: /* Call user callback */ ctx->callback(status, content, content_len, ctx->callback_ctx); /* Free response buffer */ if ( resp_buf ) ipcom_free(resp_buf); /* Context can now be destroyed */ ipike_http_fetcher_ctx_destroy(ctx); } /* *=========================================================================== * ipike_http_fetcher_send_request *=========================================================================== */ IP_STATIC void ipike_http_fetcher_send_request(Ipike_http_fetcher_ctx *ctx) { char http_request[IPIKE_HTTP_REQUEST_BUF_SIZE]; Ip_ssize_t len = 0; Ip_socklen_t err_len = 0; Ip_err err = 0; ip_assert(ctx); /* Remove the context from the list */ ipcom_list_remove(&ctx->list); /* Cancel the connect timeout */ ipike_tmo2_cancel(&ctx->tmo); /* In the normal case the socket comes from a created state, i.e. * through an asynchronous (non-blocking) connect */ if ( ctx->state == IPIKE_HTTP_SOCK_STATE_CREATED ) { /* Get any error on the socket */ err_len = sizeof(err); if ( ipcom_getsockopt(ctx->fd, IP_SOL_SOCKET, IP_SO_ERROR, &err, &err_len) < 0 ) { IPCOM_LOG1(ERR, "ipike_http_fetcher_send_request() :: getsockopt failed (%s)", ipcom_strerror(ipcom_errno)); goto error_out; } /* Check if the connect operation was successful */ if ( err != 0 ) { IPCOM_LOG1(ERR, "ipike_http_fetcher_send_request() :: connect failed (%s)", ipcom_strerror(err)); goto error_out; } /* Connect operation was successful */ ctx->state = IPIKE_HTTP_SOCK_STATE_CONNECTED; } /* We should now have a connected socket */ ip_assert( ctx->state == IPIKE_HTTP_SOCK_STATE_CONNECTED ); /* Create HTTP Request */ len = ipcom_snprintf(http_request, sizeof(http_request), IPIKE_HTTP_REQUEST_FMT_STRING, ctx->url->path, ctx->url->address); if ( len < 0 ) { IPCOM_LOG0(ERR, "ipike_http_fetcher_send_request() :: "\ "failed to create http request"); goto error_out; } IPCOM_LOG1(DEBUG2, "ipike_http_fetcher_send_request() :: "\ "sending http request \n%s", http_request); /* Send HTTP Request */ if ( ipcom_send(ctx->fd, http_request, (Ip_size_t) len, 0) != len ) { IPCOM_LOG1(ERR, "ipike_http_fetcher_send_request() :: "\ "failed to send http request (%s)", ipcom_strerror(ipcom_errno)); goto error_out; } /* Add a timeout handler for the send operation */ if (ipcom_tmo2_request(ipike.tmo_head, &ctx->tmo, (Ipcom_tmo2_func) ipike_http_fetcher_tmo_cb, ctx, ipike.http_request_timeout * 1000u) != IPCOM_SUCCESS) { IPCOM_LOG0(ERR, "ipike_http_fetcher_send_request() :: "\ "failed to register timeout"); goto error_out; } /* Insert into list */ ipcom_list_insert_last(&ctx_list_head, &ctx->list); /* All success, lets wait for the response */ return; error_out: /* Notify failure to user */ ctx->callback(IPIKE_FAILED, IP_NULL, 0, ctx->callback_ctx); /* Destroy context */ ipike_http_fetcher_ctx_destroy(ctx); } /* **************************************************************************** * 10 PUBLIC FUNCTIONS **************************************************************************** */ /* *=========================================================================== * ipike_http_fetcher_add_fds *=========================================================================== */ IP_GLOBAL int ipike_http_fetcher_add_fds(Ip_fd_set *read_fds, Ip_fd_set *write_fds) { Ipike_http_fetcher_ctx *ctx; int max = -1; for ( ctx = (Ipike_http_fetcher_ctx *) ipcom_list_first(&ctx_list_head); ctx != IP_NULL; ctx = (Ipike_http_fetcher_ctx *) ipcom_list_next(&ctx->list) ) { switch (ctx->state) { case IPIKE_HTTP_SOCK_STATE_CREATED: /* Waiting for connect to finish */ IP_FD_SET(ctx->fd, write_fds); break; case IPIKE_HTTP_SOCK_STATE_CONNECTED: /* Waiting for HTTP response */ IP_FD_SET(ctx->fd, read_fds); break; default: IPCOM_LOG1(ERR, "ipike_http_fetcher_add_fds() :: "\ "invalid socket state (%u)", ctx->state); break; } max = IP_MAX(ctx->fd, max); } return max; } /* *=========================================================================== * ipike_http_fetcher_get *=========================================================================== */ IP_GLOBAL Ipike_error ipike_http_fetcher_get( const char *url_string, Ipike_http_fetcher_callback callback, void *callback_ctx) { union Ip_sockaddr_union server_addr; struct Ip_addrinfo *result = IP_NULL;; Ipike_http_fetcher_ctx *ctx = IP_NULL; int i = 0; IPCOM_LOG1(DEBUG2, "ipike_http_fetcher_get() :: fetching \"%s\"", url_string); /* Allocate context for this request */ ctx = ipike_http_fetcher_ctx_alloc(); if ( !ctx ) { IP_PANIC(); IPCOM_LOG0(CRIT, "ipike_http_fetcher_get() :: failed to allocate memory"); goto error_out; } /* Set user callback */ ctx->callback = callback; ctx->callback_ctx = callback_ctx; /* Parse URL */ ctx->url = ipike_http_fetcher_url_parse_string(url_string); if ( !ctx->url ) { IPCOM_LOG1(ERR, "ipike_http_fetcher_get() :: invalid URL \"%s\"", url_string); goto error_out; } memset(&server_addr, 0, sizeof(server_addr)); /* Do we have a DNS name */ if ( isalpha(*ctx->url->address) ) { /* Resolve */ if ( ipcom_getaddrinfo(ctx->url->address, IP_NULL, IP_NULL, &result) != 0 ) { IPCOM_LOG1(ERR, "ipike_http_fetcher() :: failed to resolve \"%s\"", ctx->url->address); goto error_out; } /* Use the first proposed address */ memcpy(&server_addr, result->ai_addr, result->ai_addrlen); ipcom_freeaddrinfo(result); } else { #ifdef IPCOM_USE_INET if ( ipcom_inet_pton(IP_AF_INET, ctx->url->address, &server_addr.sin.sin_addr) == 1 ) { server_addr.sa.sa_family = IP_AF_INET; server_addr.sin.sin_port = ip_htons(IPIKE_HTTP_PORT); IPCOM_SA_LEN_SET(&server_addr.sa, sizeof(struct Ip_sockaddr_in)); } #endif #ifdef IPCOM_USE_INET6 if ( server_addr.sa.sa_family == 0 && ipcom_inet_pton(IP_AF_INET6, ctx->url->address, &server_addr.sin6.sin6_addr) == 1 ) { server_addr.sa.sa_family = IP_AF_INET6; IPCOM_SA_LEN_SET(&server_addr.sa, sizeof(struct Ip_sockaddr_in6)); } #endif } if ( server_addr.sa.sa_family == 0 ) { IPCOM_LOG1(ERR, "ipike_http_fetcher() :: bad address \"%s\"", ctx->url->address); goto error_out; } #ifdef IPCOM_USE_INET if ( server_addr.sa.sa_family == IP_AF_INET ) server_addr.sin.sin_port = ip_htons(IPIKE_HTTP_PORT); #endif #ifdef IPCOM_USE_INET6 if ( server_addr.sa.sa_family == IP_AF_INET6 ) server_addr.sin6.sin6_port = ip_htons(IPIKE_HTTP_PORT); #endif /* Create socket */ if ( (ctx->fd = ipcom_socket(server_addr.sa.sa_family, IP_SOCK_STREAM, IP_IPPROTO_TCP)) < 0 ) { IPCOM_LOG1(ERR, "ipike_http_fetcher() :: failed to create socket (%s)", ipcom_strerror(ipcom_errno)); goto error_out; } /* Set state to created */ ctx->state = IPIKE_HTTP_SOCK_STATE_CREATED; i = 1; /* Set socket to non-blocking */ if (ipcom_socketioctl(ctx->fd, IP_FIONBIO, (char *)&i) != 0) { IPCOM_LOG1(WARNING, "ipike_http_fetcher() :: failed to set socket to"\ " non blocking (%s)", ipcom_strerror(ipcom_errno)); goto error_out; } /* Connect */ if ( ipcom_connect(ctx->fd, (struct Ip_sockaddr *) &server_addr, IPCOM_SA_LEN_GET(&server_addr.sa)) < 0 ) { /* We expect IP_ERRNO_EINPROGRESS in the normal case, fail otherwise */ if ( ipcom_errno != IP_ERRNO_EINPROGRESS ) { IPCOM_LOG1(ERR, "ipike_http_fetcher() :: failed to connect socket (%s)", ipcom_strerror(ipcom_errno)); goto error_out; } } else { /* Socket successfully connected, can only occur if server address is a * local address */ IPCOM_LOG0(DEBUG2, "ipike_http_fetcher() :: socket immediately connected"); ctx->state = IPIKE_HTTP_SOCK_STATE_CONNECTED; } i = 0; /* Set socket to blocking */ if (ipcom_socketioctl(ctx->fd, IP_FIONBIO, (char *)&i) != 0) { IPCOM_LOG1(WARNING, "ipike_http_fetcher() :: failed to set socket to"\ " blocking (%s)", ipcom_strerror(ipcom_errno)); goto error_out; } /* Add a timeout handler for connect operation */ if (ipcom_tmo2_request(ipike.tmo_head, &ctx->tmo, (Ipcom_tmo2_func) ipike_http_fetcher_tmo_cb, ctx, ipike.http_request_timeout * 1000u) != IPCOM_SUCCESS) goto error_out; /* Insert into list */ ipcom_list_insert_last(&ctx_list_head, &ctx->list); /* It's unlikely but the socket can be in a connected state, in that case * we can send the request immediately */ if ( ctx->state == IPIKE_HTTP_SOCK_STATE_CONNECTED ) ipike_http_fetcher_send_request(ctx); /* return */ return IPIKE_SUCCESS; error_out: if ( ctx ) ipike_http_fetcher_ctx_destroy(ctx); return IPIKE_FAILED; } /* *=========================================================================== * ipike_http_fetcher_process *=========================================================================== */ IP_GLOBAL int ipike_http_fetcher_process( Ip_fd_set *read_fds, Ip_fd_set *write_fds ) { Ipike_http_fetcher_ctx *ctx = IP_NULL; Ipike_http_fetcher_ctx *next = IP_NULL; int num_processed = 0; ctx = (Ipike_http_fetcher_ctx *) ipcom_list_first(&ctx_list_head); while ( ctx ) { next = (Ipike_http_fetcher_ctx *) ipcom_list_next(&ctx->list); /* Socket ready for write? (connect done?)*/ if ( IP_FD_ISSET(ctx->fd, write_fds) ) { /* Try to send the request */ ipike_http_fetcher_send_request(ctx); num_processed++; } /* Socket ready for read? */ else if ( IP_FD_ISSET(ctx->fd, read_fds) ) { /* Process response */ ipike_http_fetcher_process_response(ctx); num_processed++; } ctx = next; } return num_processed; } /* *=========================================================================== * ipike_http_fetcher_cleanup *=========================================================================== */ IP_GLOBAL void ipike_http_fetcher_cleanup(void) { Ipike_http_fetcher_ctx *ctx; /* Close any pending http requests */ for ( ctx = (Ipike_http_fetcher_ctx *) ipcom_list_first(&ctx_list_head); ctx != IP_NULL; ctx = (Ipike_http_fetcher_ctx *) ipcom_list_first(&ctx_list_head) ) { /* Notify failures to user callback */ ctx->callback(IPIKE_FAILED, IP_NULL, 0, ctx->callback_ctx); /* Remove from list */ ipcom_list_remove(&ctx->list); /* Destroy context */ ipike_http_fetcher_ctx_destroy(ctx); } } /* **************************************************************************** * END OF FILE **************************************************************************** */