/* * Copyright (C) 2012-2019 UCloud. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://www.apache.org/licenses/LICENSE-2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ #ifdef __cplusplus extern "C" { #endif #include #include #include #include "uiot_defs.h" #include "utils_httpc.h" #include "utils_net.h" #include "utils_timer.h" #include "utils_md5.h" #define MIN_TIMEOUT (100) #define MAX_RETRY_COUNT (600) #define HTTP_CLIENT_READ_BUF_SIZE (1024) /* read payload */ #define HTTP_CLIENT_READ_HEAD_SIZE (32) /* read header */ #define HTTP_CLIENT_SEND_BUF_SIZE (1024) /* send */ #define HTTP_CLIENT_REQUEST_BUF_SIZE (300) /* send */ #define HTTP_CLIENT_MAX_URL_LEN (256) #define HTTP_RETRIEVE_MORE_DATA (1) /**< More data needs to be retrieved. */ #define HTTP_CLIENT_CHUNK_SIZE (1024) static int _utils_parse_url(const char *url, char *host, char *path) { char *host_ptr = (char *) strstr(url, "://"); uint32_t host_len = 0; uint32_t path_len; /* char *port_ptr; */ char *path_ptr; char *fragment_ptr; if (host_ptr == NULL) { return ERR_PARAM_INVALID; /* URL is invalid */ } host_ptr += 3; path_ptr = strchr(host_ptr, '/'); if (NULL == path_ptr) { return ERR_PARAM_INVALID; } host_len = path_ptr - host_ptr; memcpy(host, host_ptr, host_len); host[host_len] = '\0'; fragment_ptr = strchr(host_ptr, '#'); if (fragment_ptr != NULL) { path_len = fragment_ptr - path_ptr; } else { path_len = strlen(path_ptr); } memcpy(path, path_ptr, path_len); path[path_len] = '\0'; return SUCCESS_RET; } static int _utils_fill_tx_buffer(http_client_t *client, unsigned char *send_buf, int *send_idx, char *buf, uint32_t len) { int ret; int cp_len; int writen_len = 0; int idx = *send_idx; if (len == 0) { len = strlen(buf); } do { if ((HTTP_CLIENT_SEND_BUF_SIZE - idx) >= len) { cp_len = len; } else { cp_len = HTTP_CLIENT_SEND_BUF_SIZE - idx; } memcpy(send_buf + idx, buf + writen_len, cp_len); idx += cp_len; writen_len += cp_len; len -= cp_len; if (idx == HTTP_CLIENT_SEND_BUF_SIZE) { ret = client->net.write(&client->net, send_buf, HTTP_CLIENT_SEND_BUF_SIZE, 5000); if (ret < 0) { return (ret); } else if (ret != HTTP_CLIENT_SEND_BUF_SIZE) { return (ret == 0) ? ERR_HTTP_CLOSED : ERR_HTTP_CONN_ERROR; } idx -= ret; } } while (len); *send_idx = idx; return SUCCESS_RET; } static int _http_send_header(http_client_t *client, char *host, const char *path, int method, uint32_t size_fetched, size_t range_len, http_client_data_t *client_data) { int len; unsigned char send_buf[HTTP_CLIENT_SEND_BUF_SIZE] = {0}; char buf[HTTP_CLIENT_REQUEST_BUF_SIZE] = {0}; char *pMethod = (method == HTTP_GET) ? "GET" : (method == HTTP_POST) ? "POST" : (method == HTTP_PUT) ? "PUT" : (method == HTTP_DELETE) ? "DELETE" : (method == HTTP_HEAD) ? "HEAD" : ""; int ret; /* Send request */ memset(send_buf, 0, HTTP_CLIENT_SEND_BUF_SIZE); len = 0; /* Reset send buffer */ if(range_len != 0) { HAL_Snprintf(buf, sizeof(buf), "%s %s HTTP/1.1\r\nHost: %s\r\nRange: bytes=%d-%d\r\n", pMethod, path, host, size_fetched, size_fetched + range_len); /* Write request */ } else { HAL_Snprintf(buf, sizeof(buf), "%s %s HTTP/1.1\r\nHost: %s\r\n", pMethod, path, host); /* Write request */ } ret = _utils_fill_tx_buffer(client, send_buf, &len, buf, strlen(buf)); if (ret < 0) { /* LOG_ERROR("Could not write request"); */ return ERR_HTTP_CONN_ERROR; } /* Add user header information */ if (client->header) { _utils_fill_tx_buffer(client, send_buf, &len, (char *) client->header, strlen(client->header)); } if (client_data->post_buf != NULL) { HAL_Snprintf(buf, sizeof(buf), "Content-Length: %d\r\n", client_data->post_buf_len); _utils_fill_tx_buffer(client, send_buf, &len, buf, strlen(buf)); if (client_data->post_content_type != NULL) { HAL_Snprintf(buf, sizeof(buf), "Content-Type: %s\r\n", client_data->post_content_type); _utils_fill_tx_buffer(client, send_buf, &len, buf, strlen(buf)); } } /* Close headers */ _utils_fill_tx_buffer(client, send_buf, &len, "\r\n", 0); ret = client->net.write(&client->net, send_buf, len, 5000); if (ret <= 0) { LOG_ERROR("ret = client->net.write() = %d", ret); return (ret == 0) ? ERR_HTTP_CLOSED : ERR_HTTP_CONN_ERROR; } return SUCCESS_RET; } int _http_send_user_data(http_client_t *client, http_client_data_t *client_data, uint32_t timeout_ms) { int ret = 0; if (client_data->post_buf && client_data->post_buf_len) { ret = client->net.write(&client->net, (unsigned char *) client_data->post_buf, client_data->post_buf_len, timeout_ms); LOG_DEBUG("client_data->post_buf: %s, ret is %d", client_data->post_buf, ret); if (ret <= 0) { return (ret == 0) ? ERR_HTTP_CLOSED : ERR_HTTP_CONN_ERROR; /* Connection was closed by server */ } } return SUCCESS_RET; } /* 0 on success, err code on failure */ static int _http_recv(http_client_t *client, unsigned char *buf, int max_len, int *p_read_len, uint32_t timeout_ms) { int ret = 0; *p_read_len = 0; ret = client->net.read(&client->net, buf, max_len, timeout_ms); if (ret > 0) { *p_read_len = ret; return 0; } else if (ret == 0) { /* timeout */ return FAILURE_RET; } else { return ERR_HTTP_CONN_ERROR; } } static int _utils_check_deadloop(int len, Timer *timer, int ret, unsigned int *dead_loop_count, unsigned int *extend_count) { /* if timeout reduce to zero, it will be translated into NULL for select function in TLS lib */ /* it would lead to indefinite behavior, so we avoid it */ if (left_ms(timer) < MIN_TIMEOUT) { (*extend_count)++; countdown_ms(timer, MIN_TIMEOUT); } /* if it falls into deadloop before reconnected to internet, we just quit*/ if ((0 == len) && (0 == left_ms(timer)) && (FAILURE_RET == ret)) { (*dead_loop_count)++; if (*dead_loop_count > MAX_RETRY_COUNT) { LOG_ERROR("deadloop detected, exit"); return ERR_HTTP_CONN_ERROR; } } else { *dead_loop_count = 0; } /*if the internet connection is fixed during the loop, the download stream might be disconnected. we have to quit */ if ((0 == len) && (*extend_count > 2 * MAX_RETRY_COUNT) && (FAILURE_RET == ret)) { LOG_ERROR("extend timer for too many times, exit"); return ERR_HTTP_CONN_ERROR; } return SUCCESS_RET; } static int _utils_fill_rx_buf(int *recv_count, int len_to_write_to_response_buf, http_client_data_t *client_data, unsigned char *data) { int count = *recv_count; if (count + len_to_write_to_response_buf < client_data->response_buf_len - 1) { memcpy(client_data->response_buf + count, data, len_to_write_to_response_buf); count += len_to_write_to_response_buf; client_data->response_buf[count] = '\0'; client_data->retrieve_len -= len_to_write_to_response_buf; *recv_count = count; return SUCCESS_RET; } else { memcpy(client_data->response_buf + count, data, client_data->response_buf_len - 1 - count); client_data->response_buf[client_data->response_buf_len - 1] = '\0'; client_data->retrieve_len -= (client_data->response_buf_len - 1 - count); return HTTP_RETRIEVE_MORE_DATA; } } static int _http_get_response_body(http_client_t *client, unsigned char *data, int data_len_actually_received, uint32_t timeout_ms, http_client_data_t *client_data) { int written_response_buf_len = 0; int len_to_write_to_response_buf = 0; Timer timer; init_timer(&timer); countdown_ms(&timer, timeout_ms); /* Receive data */ /* LOG_DEBUG("Current data: %s", data); */ client_data->is_more = 1; /* the header is not received finished */ if (client_data->response_content_len == -1 && client_data->is_chunked == 0) { /* can not enter this if */ LOG_ERROR("header is not received yet"); return ERR_HTTP_CONN_ERROR; } while (1) { unsigned int dead_loop_count = 0; unsigned int extend_count = 0; do { int res; /* move previous fetched data into response_buf */ len_to_write_to_response_buf = Min(data_len_actually_received, client_data->retrieve_len); res = _utils_fill_rx_buf(&written_response_buf_len, len_to_write_to_response_buf, client_data, data); if (HTTP_RETRIEVE_MORE_DATA == res) { return HTTP_RETRIEVE_MORE_DATA; } /* get data from internet and put into "data" buf temporary */ if (client_data->retrieve_len) { int ret; int max_len_to_receive = Min(HTTP_CLIENT_CHUNK_SIZE - 1, client_data->response_buf_len - 1 - written_response_buf_len); max_len_to_receive = Min(max_len_to_receive, client_data->retrieve_len); ret = _http_recv(client, data, max_len_to_receive, &data_len_actually_received, timeout_ms); if (ret == ERR_HTTP_CONN_ERROR) { return ret; } LOG_DEBUG("Total- remaind Payload: %d Bytes; currently Read: %d Bytes", client_data->retrieve_len, data_len_actually_received); ret = _utils_check_deadloop(data_len_actually_received, &timer, ret, &dead_loop_count, &extend_count); if (ERR_HTTP_CONN_ERROR == ret) { return ret; } } } while (client_data->retrieve_len); client_data->is_more = 0; break; } return SUCCESS_RET; } static int _http_parse_response_header(http_client_t *client, char *data, int len, uint32_t timeout_ms, http_client_data_t *client_data) { int crlf_pos; char *tmp_ptr, *ptr_body_end; int new_trf_len, ret; char *crlf_ptr; client_data->response_content_len = -1; /* http client response */ /* HTTP/1.1 200 OK(CRLF) ...(CRLF) (CRLF) [] */ crlf_ptr = strstr(data, "\r\n"); if (crlf_ptr == NULL) { LOG_ERROR("\r\n not found"); return ERR_HTTP_UNRESOLVED_DNS; } crlf_pos = crlf_ptr - data; data[crlf_pos] = '\0'; client->response_code = atoi(data + 9); LOG_DEBUG("Reading headers: %s", data); memmove(data, &data[crlf_pos + 2], len - (crlf_pos + 2) + 1); /* Be sure to move NULL-terminating char as well */ len -= (crlf_pos + 2); /* remove status_line length */ client_data->is_chunked = 0; /*If not ending of response body*/ /* try to read more header again until find response head ending "\r\n\r\n" */ while (NULL == (ptr_body_end = strstr(data, "\r\n\r\n"))) { /* try to read more header */ ret = _http_recv(client, (unsigned char *) (data + len), HTTP_CLIENT_READ_HEAD_SIZE, &new_trf_len, timeout_ms); if (ret == ERR_HTTP_CONN_ERROR) { return ret; } len += new_trf_len; data[len] = '\0'; } /* parse response_content_len */ if (NULL != (tmp_ptr = strstr(data, "Content-Length"))) { client_data->response_content_len = atoi(tmp_ptr + strlen("Content-Length: ")); client_data->retrieve_len = client_data->response_content_len; } else { LOG_ERROR("Could not parse header"); return ERR_HTTP_CONN_ERROR; } /* remove header length */ /* len is Had read body's length */ /* if client_data->response_content_len != 0, it is know response length */ /* the remain length is client_data->response_content_len - len */ len = len - (ptr_body_end + 4 - data); memmove(data, ptr_body_end + 4, len + 1); client_data->response_received_len += len; return _http_get_response_body(client, (unsigned char *) data, len, timeout_ms, client_data); } static int _http_connect(http_client_t *client) { int retry_max = 3; int retry_cnt = 1; int retry_interval = 1000; int rc = -1; do { client->net.handle = 0; LOG_DEBUG("calling TCP or TLS connect HAL for [%d/%d] iteration", retry_cnt, retry_max); rc = client->net.connect(&client->net); if (0 != rc) { client->net.disconnect(&client->net); LOG_ERROR("TCP or TLS connect failed, rc = %d", rc); HAL_SleepMs(retry_interval); continue; } else { LOG_DEBUG("rc = client->net.connect() = %d, success @ [%d/%d] iteration", rc, retry_cnt, retry_max); break; } } while (++retry_cnt <= retry_max); return SUCCESS_RET; } int _http_send_request(http_client_t *client, const char *url, HTTP_Request_Method method, uint32_t size_fetched, size_t range_len, http_client_data_t *client_data, uint32_t timeout_ms) { int ret = ERR_HTTP_CONN_ERROR; if (0 == client->net.handle) { return -1; } int rc; char host[HTTP_CLIENT_MAX_URL_LEN] = {0}; char path[HTTP_CLIENT_MAX_URL_LEN] = {0}; rc = _utils_parse_url(url, host, path); if (rc != SUCCESS_RET) { return rc; } ret = _http_send_header(client, host, path, method, size_fetched, range_len, client_data); if (ret != 0) { return ERR_HTTP_CONN_ERROR; } if (method == HTTP_POST || method == HTTP_PUT) { ret = _http_send_user_data(client, client_data,timeout_ms); if (ret < 0) { ret = ERR_HTTP_CONN_ERROR; } } return ret; } static int _http_client_recv_response(http_client_t *client, uint32_t timeout_ms, http_client_data_t *client_data) { int read_len = 0, ret = ERR_HTTP_CONN_ERROR; char buf[HTTP_CLIENT_READ_BUF_SIZE] = {0}; if (0 == client->net.handle) { LOG_ERROR("no connection have been established"); return ret; } if (client_data->is_more) { client_data->response_buf[0] = '\0'; ret = _http_get_response_body(client, (unsigned char *) buf, read_len, timeout_ms, client_data); } else { client_data->is_more = 1; /* try to read header */ ret = _http_recv(client, (unsigned char *) buf, HTTP_CLIENT_READ_HEAD_SIZE, &read_len, timeout_ms); if (ret != 0) { return ret; } buf[read_len] = '\0'; if (read_len) { ret = _http_parse_response_header(client, buf, read_len, timeout_ms, client_data); } } return ret; } int http_client_connect(http_client_t *client, const char *url, int port, const char *ca_crt) { if (client->net.handle != 0) { LOG_ERROR("http client has connected to host!"); return ERR_HTTP_CONN_ERROR; } int rc; char host[HTTP_CLIENT_MAX_URL_LEN] = {0}; char path[HTTP_CLIENT_MAX_URL_LEN] = {0}; rc = _utils_parse_url(url, host, path); if (rc != SUCCESS_RET) { return rc; } rc = utils_net_init(&client->net, host, port, SSL_CA_VERIFY_REQUIRED, ca_crt); if (rc != SUCCESS_RET) { return rc; } rc = _http_connect(client); if (rc != SUCCESS_RET) { LOG_ERROR("_http_connect error, rc = %d", rc); http_client_close(client); } else { LOG_DEBUG("http client connect success"); } return rc; } int http_client_common(http_client_t *client, const char *url, int port, const char *ca_crt, HTTP_Request_Method method, http_client_data_t *client_data, uint32_t timeout_ms) { int rc; if (client->net.handle == 0) { rc = http_client_connect(client, url, port, ca_crt); if (rc != SUCCESS_RET) { return rc; } } rc = _http_send_request(client, url, method, 0, 0, client_data, timeout_ms); if (rc != SUCCESS_RET) { LOG_ERROR("http_send_request error, rc = %d", rc); http_client_close(client); return rc; } return SUCCESS_RET; } int http_client_recv_data(http_client_t *client, uint32_t timeout_ms, http_client_data_t *client_data) { int rc; if ((NULL != client_data->response_buf) && (0 != client_data->response_buf_len)) { rc = _http_client_recv_response(client, timeout_ms, client_data); if (rc < 0) { LOG_ERROR("_http_client_recv_response is error, rc = %d", rc); http_client_close(client); return rc; } } return SUCCESS_RET; } void http_client_close(http_client_t *client) { if (client->net.handle > 0) { client->net.disconnect(&client->net); } client->net.handle = 0; LOG_INFO("client disconnected"); } void http_client_file_md5(char* file_path, char *output) { iot_md5_context ctx; utils_md5_init(&ctx); utils_md5_starts(&ctx); char *buffer = (char *)HAL_Malloc(1024); if (NULL == buffer) { return; } memset(buffer,0,1024); uint32_t count = 0; FILE *fp = fopen(file_path, "rb+"); if(NULL == fp) { return; } while((count = fread(buffer,1,1024,fp))){ utils_md5_update(&ctx, (unsigned char *)buffer, count); } utils_md5_finish_hb2hex(&ctx, output); utils_md5_free(&ctx); fclose(fp); HAL_Free(buffer); } void http_client_buffer_md5(char* buffer, uint32_t buffer_len, char *output) { iot_md5_context ctx; utils_md5_init(&ctx); utils_md5_starts(&ctx); utils_md5_update(&ctx, (unsigned char *)buffer, buffer_len); utils_md5_finish_hb2hex(&ctx, output); utils_md5_free(&ctx); return; } #ifdef __cplusplus } #endif