| //! @file |
| //! |
| //! Copyright (c) Memfault, Inc. |
| //! See LICENSE for details |
| //! |
| //! @brief |
| //! See header for more details |
| |
| #include <ctype.h> |
| #include <limits.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <string.h> |
| |
| #include "memfault/core/compiler.h" |
| #include "memfault/core/debug_log.h" |
| #include "memfault/core/math.h" |
| #include "memfault/core/platform/device_info.h" |
| #include "memfault/http/http_client.h" |
| #include "memfault/http/utils.h" |
| #include "memfault/version.h" |
| |
| //! Default buffer size for the URL-encoded device info parameters. This may |
| //! need to be set higher by the user if there are particularly long device info |
| //! strings |
| #ifndef MEMFAULT_DEVICE_INFO_URL_ENCODED_MAX_LEN |
| #define MEMFAULT_DEVICE_INFO_URL_ENCODED_MAX_LEN (48) |
| #endif |
| |
| static bool prv_write_msg(MfltHttpClientSendCb write_callback, void *ctx, const char *msg, |
| size_t msg_len, size_t max_len) { |
| if (msg_len >= max_len) { |
| return false; |
| } |
| |
| return write_callback(msg, msg_len, ctx); |
| } |
| |
| static bool prv_write_crlf(MfltHttpClientSendCb write_callback, void *ctx) { |
| #define END_HEADER_SECTION "\r\n" |
| const size_t end_hdr_section_len = MEMFAULT_STATIC_STRLEN(END_HEADER_SECTION); |
| return write_callback(END_HEADER_SECTION, end_hdr_section_len, ctx); |
| } |
| |
| // NB: All HTTP/1.1 requests must provide a Host Header |
| // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Host |
| static bool prv_write_host_hdr(MfltHttpClientSendCb write_callback, void *ctx, const void *host, |
| size_t host_len) { |
| #define HOST_HDR_BEGIN "Host:" |
| const size_t host_hdr_begin_len = MEMFAULT_STATIC_STRLEN(HOST_HDR_BEGIN); |
| if (!write_callback(HOST_HDR_BEGIN, host_hdr_begin_len, ctx)) { |
| return false; |
| } |
| |
| if (!write_callback(host, host_len, ctx)) { |
| return false; |
| } |
| |
| return prv_write_crlf(write_callback, ctx); |
| } |
| |
| static bool prv_write_user_agent_hdr(MfltHttpClientSendCb write_callback, void *ctx) { |
| #define USER_AGENT_HDR "User-Agent:MemfaultSDK/" MEMFAULT_SDK_VERSION_STR "\r\n" |
| const size_t user_agent_hdr_len = MEMFAULT_STATIC_STRLEN(USER_AGENT_HDR); |
| return write_callback(USER_AGENT_HDR, user_agent_hdr_len, ctx); |
| } |
| |
| static bool prv_write_project_key_hdr(MfltHttpClientSendCb write_callback, void *ctx) { |
| #define PROJECT_KEY_HDR_BEGIN "Memfault-Project-Key:" |
| const size_t project_key_begin_len = MEMFAULT_STATIC_STRLEN(PROJECT_KEY_HDR_BEGIN); |
| if (!write_callback(PROJECT_KEY_HDR_BEGIN, project_key_begin_len, ctx)) { |
| return false; |
| } |
| |
| const char *project_key = g_mflt_http_client_config.api_key; |
| size_t project_key_len = strlen(project_key); |
| |
| if (!write_callback(project_key, project_key_len, ctx)) { |
| return false; |
| } |
| |
| return prv_write_crlf(write_callback, ctx); |
| } |
| |
| bool memfault_http_start_chunk_post(MfltHttpClientSendCb write_callback, void *ctx, |
| size_t content_body_length) { |
| // Request built will look like this: |
| // POST /api/v0/chunks/<device_serial> HTTP/1.1\r\n |
| // Host:chunks.memfault.com\r\n |
| // User-Agent: MemfaultSDK/0.4.2\r\n |
| // Memfault-Project-Key:<PROJECT_KEY>\r\n |
| // Content-Type:application/octet-stream\r\n |
| // Content-Length:<content_body_length>\r\n |
| // \r\n |
| |
| sMemfaultDeviceInfo device_info; |
| memfault_http_get_device_info(&device_info); |
| |
| char buffer[100]; |
| const size_t max_msg_len = sizeof(buffer); |
| size_t msg_len = (size_t)snprintf(buffer, sizeof(buffer), "POST /api/v0/chunks/%s HTTP/1.1\r\n", |
| device_info.device_serial); |
| if (!prv_write_msg(write_callback, ctx, buffer, msg_len, max_msg_len)) { |
| return false; |
| } |
| |
| const char *host = MEMFAULT_HTTP_GET_CHUNKS_API_HOST(); |
| const size_t host_len = strlen(host); |
| if (!prv_write_host_hdr(write_callback, ctx, host, host_len)) { |
| return false; |
| } |
| |
| if (!prv_write_user_agent_hdr(write_callback, ctx)) { |
| return false; |
| } |
| |
| if (!prv_write_project_key_hdr(write_callback, ctx)) { |
| return false; |
| } |
| |
| #define CONTENT_TYPE "Content-Type:application/octet-stream\r\n" |
| const size_t content_type_len = MEMFAULT_STATIC_STRLEN(CONTENT_TYPE); |
| if (!write_callback(CONTENT_TYPE, content_type_len, ctx)) { |
| return false; |
| } |
| |
| msg_len = |
| (size_t)snprintf(buffer, sizeof(buffer), "Content-Length:%d\r\n", (int)content_body_length); |
| |
| return prv_write_msg(write_callback, ctx, buffer, msg_len, max_msg_len) && |
| prv_write_crlf(write_callback, ctx); |
| } |
| |
| static bool prv_write_qparam(MfltHttpClientSendCb write_callback, void *ctx, const void *name, |
| size_t name_strlen, const char *value) { |
| return write_callback("&", 1, ctx) && write_callback(name, name_strlen, ctx) && |
| write_callback("=", 1, ctx) && write_callback(value, strlen(value), ctx); |
| } |
| |
| bool memfault_http_get_latest_ota_payload_url(MfltHttpClientSendCb write_callback, void *ctx) { |
| // Request built will look like this: |
| // GET |
| // /api/v0/releases/latest/url&device_serial=<>&hardware_version=<>&software_type=<>¤t_version=<> |
| // HTTP/1.1\r\n Host:<ota download url host>\r\n User-Agent: MemfaultSDK/0.4.2\r\n |
| // Memfault-Project-Key:<PROJECT_KEY>\r\n |
| // \r\n |
| |
| #define LATEST_REQUEST_LINE_BEGIN "GET /api/v0/releases/latest/url?" |
| const size_t request_line_begin_len = MEMFAULT_STATIC_STRLEN(LATEST_REQUEST_LINE_BEGIN); |
| if (!write_callback(LATEST_REQUEST_LINE_BEGIN, request_line_begin_len, ctx)) { |
| return false; |
| } |
| |
| sMemfaultDeviceInfo device_info = { 0 }; |
| memfault_http_get_device_info(&device_info); |
| |
| #define DEVICE_SERIAL_QPARAM "device_serial" |
| #define HARDWARE_VERSION_QPARAM "hardware_version" |
| #define SOFTWARE_TYPE_QPARAM "software_type" |
| #define CURRENT_VERSION_QPARAM "current_version" |
| |
| #define MEMFAULT_STATIC_STRLEN(s) (sizeof(s) - 1) |
| |
| const struct qparam_values_s { |
| const char *name; |
| size_t name_strlen; |
| const char *value; |
| } qparam_values[] = { |
| { |
| DEVICE_SERIAL_QPARAM, |
| MEMFAULT_STATIC_STRLEN(DEVICE_SERIAL_QPARAM), |
| device_info.device_serial, |
| }, |
| { |
| HARDWARE_VERSION_QPARAM, |
| MEMFAULT_STATIC_STRLEN(HARDWARE_VERSION_QPARAM), |
| device_info.hardware_version, |
| }, |
| { |
| SOFTWARE_TYPE_QPARAM, |
| MEMFAULT_STATIC_STRLEN(SOFTWARE_TYPE_QPARAM), |
| device_info.software_type, |
| }, |
| { |
| CURRENT_VERSION_QPARAM, |
| MEMFAULT_STATIC_STRLEN(CURRENT_VERSION_QPARAM), |
| device_info.software_version, |
| }, |
| }; |
| |
| // URL encode the qparam values before writing them |
| for (size_t i = 0; i < MEMFAULT_ARRAY_SIZE(qparam_values); i++) { |
| const struct qparam_values_s *qparam_value = &qparam_values[i]; |
| |
| char qparam_encoded_buffer[MEMFAULT_DEVICE_INFO_URL_ENCODED_MAX_LEN]; |
| int rv = memfault_http_urlencode(qparam_value->value, strlen(qparam_value->value), |
| qparam_encoded_buffer, sizeof(qparam_encoded_buffer)); |
| if (rv != 0) { |
| MEMFAULT_LOG_ERROR("Failed to URL encode qparam value: %s", qparam_value->value); |
| return false; |
| } |
| |
| if (!prv_write_qparam(write_callback, ctx, qparam_value->name, qparam_value->name_strlen, |
| qparam_encoded_buffer)) { |
| return false; |
| } |
| } |
| |
| #define LATEST_REQUEST_LINE_END " HTTP/1.1\r\n" |
| const size_t request_line_end_len = MEMFAULT_STATIC_STRLEN(LATEST_REQUEST_LINE_END); |
| if (!write_callback(LATEST_REQUEST_LINE_END, request_line_end_len, ctx)) { |
| return false; |
| } |
| |
| const char *host = MEMFAULT_HTTP_GET_DEVICE_API_HOST(); |
| const size_t host_len = strlen(host); |
| if (!prv_write_host_hdr(write_callback, ctx, host, host_len)) { |
| return false; |
| } |
| |
| if (!prv_write_user_agent_hdr(write_callback, ctx)) { |
| return false; |
| } |
| |
| if (!prv_write_project_key_hdr(write_callback, ctx)) { |
| return false; |
| } |
| |
| return prv_write_crlf(write_callback, ctx); |
| } |
| |
| static bool prv_is_number(char c) { |
| return ((c) >= '0' && (c) <= '9'); |
| } |
| |
| static size_t prv_count_spaces(const char *line, size_t start_offset, size_t total_len) { |
| size_t spaces_found = 0; |
| for (; start_offset < total_len; start_offset++) { |
| if (line[start_offset] != ' ') { |
| break; |
| } |
| spaces_found++; |
| } |
| |
| return spaces_found; |
| } |
| |
| static char prv_lower(char in) { |
| char maybe_lower_c = in | 0x20; |
| if (maybe_lower_c >= 'a' && maybe_lower_c <= 'z') { |
| // return lower case if value is actually in A-Z, a-z range |
| return maybe_lower_c; |
| } |
| |
| return in; |
| } |
| |
| // depending on the libc used, strcasecmp isn't always available so let's |
| // use a simple variant here |
| static bool prv_strcasecmp(const char *line, const char *search_str, size_t str_len) { |
| for (size_t idx = 0; idx < str_len; idx++) { |
| char lower_c = prv_lower(line[idx]); |
| |
| if (lower_c != search_str[idx]) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| static int prv_str_to_dec(const char *buf, size_t buf_len, int *value_out) { |
| int result = 0; |
| size_t idx = 0; |
| for (; idx < buf_len; idx++) { |
| char c = buf[idx]; |
| if (c == ' ') { |
| break; |
| } |
| |
| if (!prv_is_number(c)) { |
| // unexpected character encountered |
| return -1; |
| } |
| |
| int digit = c - '0'; |
| |
| // there's no limit to the size of a Content-Length value per specification: |
| // https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.2 |
| // |
| // status code is required to be 3 digits per: |
| // https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.2 |
| // |
| // any value that we can't fit in our variable is an error |
| if ((INT_MAX / 10) < (result + digit)) { |
| return -1; // result will overflow |
| } |
| |
| result = (result * 10) + digit; |
| } |
| |
| *value_out = result; |
| return (int)idx; |
| } |
| |
| //! @return true if parsing was successful, false if a parse error occurred |
| //! @note The only header we are interested in for the simple memfault response |
| //! parser is "Content-Length" to figure out how long the body is so that's all |
| //! this parser looks for |
| static bool prv_parse_header(char *line, size_t len, int *content_length_out) { |
| #define CONTENT_LENGTH "content-length" |
| const size_t content_hdr_len = MEMFAULT_STATIC_STRLEN(CONTENT_LENGTH); |
| if (len < content_hdr_len) { |
| return true; |
| } |
| |
| size_t idx = 0; |
| if (!prv_strcasecmp(line, CONTENT_LENGTH, content_hdr_len)) { |
| return true; |
| } |
| |
| idx += content_hdr_len; |
| |
| idx += prv_count_spaces(line, idx, len); |
| if (line[idx] != ':') { |
| return false; |
| } |
| idx++; |
| |
| idx += prv_count_spaces(line, idx, len); |
| |
| const int bytes_processed = prv_str_to_dec(&line[idx], len - idx, content_length_out); |
| // should find at least one digit |
| return (bytes_processed > 0); |
| } |
| |
| static bool prv_parse_status_line(char *line, size_t len, int *http_status) { |
| #define HTTP_VERSION "HTTP/1." |
| const size_t http_ver_len = MEMFAULT_STATIC_STRLEN(HTTP_VERSION); |
| if (len < http_ver_len) { |
| return false; |
| } |
| if (memcmp(line, HTTP_VERSION, http_ver_len) != 0) { |
| return false; |
| } |
| size_t idx = http_ver_len; |
| if (len < idx + 1) { |
| return false; |
| } |
| |
| // now we should have single byte minor version |
| if (!prv_is_number(line[idx])) { |
| return false; |
| } |
| idx++; |
| |
| // now we should have at least one space |
| const size_t num_spaces = prv_count_spaces(line, idx, len); |
| if (num_spaces == 0) { |
| return false; |
| } |
| idx += num_spaces; |
| |
| const size_t status_code_num_digits = 3; |
| size_t status_code_end = idx + status_code_num_digits; |
| if (len < status_code_end) { |
| return false; |
| } |
| |
| const int bytes_processed = prv_str_to_dec(&line[idx], status_code_end, http_status); |
| |
| // NB: the remainder line is the "Reason-Phrase" which we don't care about |
| return (bytes_processed == (int)status_code_num_digits); |
| } |
| |
| static bool prv_is_cr_lf(char *buf) { |
| return buf[0] == '\r' && buf[1] == '\n'; |
| } |
| |
| static bool prv_parse_http_response(sMemfaultHttpResponseContext *ctx, const void *data, |
| size_t data_len, bool parse_header_only) { |
| ctx->data_bytes_processed = 0; |
| |
| const char *chars = (const char *)data; |
| char *line_buf = &ctx->line_buf[0]; |
| for (size_t i = 0; i < data_len; i++, ctx->data_bytes_processed++) { |
| const char c = chars[i]; |
| if (ctx->phase == kMfltHttpParsePhase_ExpectingBody) { |
| if (parse_header_only) { |
| return true; |
| } |
| |
| // Just eat the message body so we can handle response lengths of arbitrary size |
| ctx->content_received++; |
| |
| if (ctx->line_len < (sizeof(ctx->line_buf) - 1)) { |
| line_buf[ctx->line_len] = c; |
| ctx->line_len++; |
| } |
| |
| bool done = (ctx->content_received == ctx->content_length); |
| if (!done) { |
| continue; |
| } |
| ctx->line_buf[ctx->line_len] = '\0'; |
| ctx->http_body = ctx->line_buf; |
| ctx->data_bytes_processed++; |
| return done; |
| } |
| |
| if (ctx->line_len >= sizeof(ctx->line_buf)) { |
| if (ctx->phase == kMfltHttpParsePhase_ExpectingHeader) { |
| // We want to truncate headers at index sizeof(line_buf)-2 |
| // so we can place the source's CR/LF sequence at the end |
| // when the source is finally exhausted. |
| static const size_t lf_idx = sizeof(ctx->line_buf) - 2; |
| static const size_t cr_idx = sizeof(ctx->line_buf) - 1; |
| if (c == '\r') { |
| ctx->line_buf[lf_idx] = c; |
| } else if (c == '\n') { |
| ctx->line_buf[cr_idx] = c; |
| } |
| } else { |
| // It's too long so set a parse error and return done. |
| ctx->parse_error = MfltHttpParseStatus_HeaderTooLongError; |
| return true; |
| } |
| } else { |
| line_buf[ctx->line_len] = c; |
| ctx->line_len++; |
| } |
| |
| if (ctx->line_len < 2) { |
| continue; |
| } |
| |
| const size_t len = ctx->line_len - 2; |
| if (prv_is_cr_lf(&line_buf[len])) { |
| ctx->line_len = 0; |
| line_buf[len] = '\0'; |
| |
| // The first line in a http response is the HTTP "Status-Line" |
| if (ctx->phase == kMfltHttpParsePhase_ExpectingStatusLine) { |
| if (!prv_parse_status_line(line_buf, len, &ctx->http_status_code)) { |
| ctx->parse_error = MfltHttpParseStatus_ParseStatusLineError; |
| return true; |
| } |
| ctx->phase = kMfltHttpParsePhase_ExpectingHeader; |
| } else if (ctx->phase == kMfltHttpParsePhase_ExpectingHeader) { |
| if (!prv_parse_header(line_buf, len, &ctx->content_length)) { |
| ctx->parse_error = MfltHttpParseStatus_ParseHeaderError; |
| return true; |
| } |
| |
| if (len != 0) { |
| continue; |
| } |
| // We've reached the end of headers marker |
| if (ctx->content_length == 0) { |
| // no body to read |
| return true; |
| } |
| ctx->phase = kMfltHttpParsePhase_ExpectingBody; |
| } |
| } |
| } |
| return false; |
| } |
| |
| bool memfault_http_parse_response(sMemfaultHttpResponseContext *ctx, const void *data, |
| size_t data_len) { |
| const bool parse_header_only = false; |
| return prv_parse_http_response(ctx, data, data_len, parse_header_only); |
| } |
| |
| bool memfault_http_parse_response_header(sMemfaultHttpResponseContext *ctx, const void *data, |
| size_t data_len) { |
| const bool parse_header_only = true; |
| return prv_parse_http_response(ctx, data, data_len, parse_header_only); |
| } |
| |
| static bool prv_find_first_occurrence(const char *line, size_t total_len, char c, |
| size_t *offset_out) { |
| for (size_t offset = 0; offset < total_len; offset++) { |
| if (line[offset] == c) { |
| *offset_out = offset; |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| static bool prv_find_last_occurrence(const char *line, size_t total_len, char c, |
| size_t *offset_out) { |
| for (int offset = (int)total_len - 1; offset >= 0; offset--) { |
| if (line[offset] == c) { |
| *offset_out = (size_t)offset; |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| static bool prv_startswith_str(const char *match, size_t match_len, const void *uri, |
| size_t total_len) { |
| if (total_len < match_len) { |
| return false; |
| } |
| |
| return prv_strcasecmp((const char *)uri, match, match_len); |
| } |
| |
| static size_t prv_is_http_or_https_scheme(const void *uri, size_t total_len, |
| eMemfaultUriScheme *scheme, uint32_t *default_port) { |
| #define HTTPS_SCHEME_WITH_AUTHORITY "https://" |
| const size_t https_scheme_with_authority_len = |
| MEMFAULT_STATIC_STRLEN(HTTPS_SCHEME_WITH_AUTHORITY); |
| if (prv_startswith_str(HTTPS_SCHEME_WITH_AUTHORITY, https_scheme_with_authority_len, uri, |
| total_len)) { |
| *scheme = kMemfaultUriScheme_Https; |
| *default_port = 443; |
| return https_scheme_with_authority_len; |
| } |
| |
| #define HTTP_SCHEME_WITH_AUTHORITY "http://" |
| const size_t http_scheme_with_authority_len = MEMFAULT_STATIC_STRLEN(HTTP_SCHEME_WITH_AUTHORITY); |
| if (prv_startswith_str(HTTP_SCHEME_WITH_AUTHORITY, http_scheme_with_authority_len, uri, |
| total_len)) { |
| *scheme = kMemfaultUriScheme_Http; |
| *default_port = 80; |
| return http_scheme_with_authority_len; |
| } |
| |
| *scheme = kMemfaultUriScheme_Unrecognized; |
| return 0; |
| } |
| |
| bool memfault_http_parse_uri(const char *uri, size_t uri_len, sMemfaultUriInfo *uri_info_out) { |
| size_t host_len = uri_len; // we will adjust as we parse the uri |
| |
| eMemfaultUriScheme scheme; |
| uint32_t port = 0; |
| const size_t bytes_consumed = prv_is_http_or_https_scheme(uri, host_len, &scheme, &port); |
| if (bytes_consumed == 0) { |
| return false; |
| } |
| host_len -= bytes_consumed; |
| |
| const char *authority = uri + bytes_consumed; |
| // Authority ends with a "/" when followed by a "path" or is the length of the string |
| size_t offset; |
| const char *path = NULL; |
| size_t path_len = 0; |
| if (prv_find_first_occurrence(authority, host_len, '/', &offset)) { |
| path = &authority[offset]; |
| path_len = host_len - offset; |
| host_len = offset; |
| } |
| |
| if (prv_find_first_occurrence(authority, host_len, '@', &offset)) { |
| offset++; // skip past the '@' - no use for username/password today |
| if (host_len == offset) { |
| return false; |
| } |
| |
| authority += offset; |
| host_len -= offset; |
| } |
| |
| // are we dealing with an IP-Literal? |
| size_t port_begin_search_offset = 0; |
| if (authority[0] == '[') { |
| if (!prv_find_last_occurrence(authority, host_len, ']', &port_begin_search_offset)) { |
| return false; |
| } |
| } |
| |
| // was a port number included? |
| if (prv_find_last_occurrence(authority, host_len, ':', &offset)) { |
| if (offset >= port_begin_search_offset) { |
| const size_t port_begin_offset = offset + 1 /* skip ':' */; |
| // recover the port |
| int dec_num; |
| const int bytes_processed = |
| prv_str_to_dec(&authority[port_begin_offset], host_len - port_begin_offset, &dec_num); |
| if (bytes_processed <= 0) { |
| return false; |
| } |
| port = (uint32_t)dec_num; |
| host_len = offset; |
| } |
| } |
| |
| if (host_len == 0) { |
| return false; // no host name located! |
| } |
| |
| *uri_info_out = (sMemfaultUriInfo){ |
| .scheme = scheme, |
| .host = authority, |
| .host_len = host_len, |
| .path = path, |
| .path_len = path_len, |
| .port = port, |
| }; |
| return true; |
| } |
| |
| bool memfault_http_get_ota_payload(MfltHttpClientSendCb write_callback, void *ctx, const char *url, |
| size_t url_len) { |
| // Request built will look like this: |
| // GET <Request-URI from url> HTTP/1.1\r\n |
| // Host:<Host from url>\r\n |
| // User-Agent: MemfaultSDK/0.4.2\r\n |
| // \r\n |
| |
| sMemfaultUriInfo info; |
| if (!memfault_http_parse_uri(url, url_len, &info)) { |
| return false; |
| } |
| |
| #define DOWNLOAD_REQUEST_LINE_BEGIN "GET " |
| const size_t download_line_begin_len = MEMFAULT_STATIC_STRLEN(DOWNLOAD_REQUEST_LINE_BEGIN); |
| if (!write_callback(DOWNLOAD_REQUEST_LINE_BEGIN, download_line_begin_len, ctx)) { |
| return false; |
| } |
| |
| bool success = (info.path != NULL) ? write_callback(info.path, info.path_len, ctx) : |
| write_callback("/", 1, ctx); |
| if (!success) { |
| return false; |
| } |
| |
| #define DOWNLOAD_REQUEST_LINE_END " HTTP/1.1\r\n" |
| const size_t download_request_line_end_len = MEMFAULT_STATIC_STRLEN(DOWNLOAD_REQUEST_LINE_END); |
| if (!write_callback(DOWNLOAD_REQUEST_LINE_END, download_request_line_end_len, ctx)) { |
| return false; |
| } |
| |
| if (!prv_write_host_hdr(write_callback, ctx, info.host, info.host_len)) { |
| return false; |
| } |
| |
| if (!prv_write_user_agent_hdr(write_callback, ctx)) { |
| return false; |
| } |
| |
| return prv_write_crlf(write_callback, ctx); |
| } |
| |
| static bool prv_is_unreserved(char c) { |
| return isalnum((uint8_t)c) || c == '-' || c == '_' || c == '.' || c == '~'; |
| } |
| |
| bool memfault_http_needs_escape(const char *str, size_t len) { |
| for (size_t i = 0; i < len; i++) { |
| if (!prv_is_unreserved(str[i])) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| int memfault_http_urlencode(const char *inbuf, size_t inbuf_len, char *outbuf, size_t outbuf_len) { |
| // null check |
| if (!inbuf || !outbuf || !inbuf_len || !outbuf_len) { |
| return -1; |
| } |
| |
| while (inbuf_len--) { |
| if (outbuf_len < 2) { |
| // ran out of room before encoding the full input, error. there needs to |
| // be 1 spare character for null term |
| return -1; |
| } |
| char c = *inbuf++; |
| if (prv_is_unreserved(c)) { |
| *outbuf++ = c; |
| outbuf_len--; |
| } else { |
| if (outbuf_len < 4) { |
| // not enough room for encoded character and null term |
| return -1; |
| } |
| // paste encoding (+ null term) |
| snprintf(outbuf, 4, "%%%02X", (uint8_t)c); |
| outbuf += 3; |
| outbuf_len -= 3; |
| } |
| } |
| |
| // null terminate |
| *outbuf = '\0'; |
| |
| return 0; |
| } |
| |
| void memfault_http_get_device_info(sMemfaultDeviceInfo *info) { |
| // Use the user provided callback if available, otherwise use the standard |
| // platform implementation |
| if (g_mflt_http_client_config.get_device_info != NULL) { |
| g_mflt_http_client_config.get_device_info(info); |
| return; |
| } |
| memfault_platform_get_device_info(info); // note: HTTP functions should always call this |
| } |