// SPDX-License-Identifier: GPL-3.0-or-later
#include "libnetdata/libnetdata.h"
#include "https_client.h"
#include "mqtt_websockets/c-rbuf/include/ringbuffer.h"
#include "aclk_util.h"
#include "daemon/global_statistics.h"
#define DEFAULT_CHUNKED_RESPONSE_BUFFER_SIZE (4096)
enum http_parse_state {
HTTP_PARSE_INITIAL = 0,
HTTP_PARSE_HEADERS,
HTTP_PARSE_CONTENT
};
static const char *http_req_type_to_str(http_req_type_t req) {
switch (req) {
case HTTP_REQ_GET:
return "GET";
case HTTP_REQ_POST:
return "POST";
case HTTP_REQ_CONNECT:
return "CONNECT";
default:
return "unknown";
}
}
#define TRANSFER_ENCODING_CHUNKED (-2)
typedef struct {
enum http_parse_state state;
int content_length;
int http_code;
// for chunked data only
char *chunked_response;
size_t chunked_response_size;
size_t chunked_response_written;
enum chunked_content_state {
CHUNKED_CONTENT_CHUNK_SIZE = 0,
CHUNKED_CONTENT_CHUNK_DATA,
CHUNKED_CONTENT_CHUNK_END_CRLF,
CHUNKED_CONTENT_FINAL_CRLF
} chunked_content_state;
size_t chunk_size;
size_t chunk_got;
} http_parse_ctx;
#define HTTP_PARSE_CTX_INITIALIZER { .state = HTTP_PARSE_INITIAL, .content_length = -1, .http_code = 0 }
static inline void http_parse_ctx_clear(http_parse_ctx *ctx) {
ctx->state = HTTP_PARSE_INITIAL;
ctx->content_length = -1;
ctx->http_code = 0;
}
#define POLL_TO_MS 100
#define NEED_MORE_DATA 0
#define PARSE_SUCCESS 1
#define PARSE_ERROR -1
#define HTTP_LINE_TERM "\x0D\x0A"
#define RESP_PROTO "HTTP/1.1 "
#define HTTP_KEYVAL_SEPARATOR ": "
#define HTTP_HDR_BUFFER_SIZE 1024
#define PORT_STR_MAX_BYTES 12
static int process_http_hdr(http_parse_ctx *parse_ctx, const char *key, const char *val)
{
// currently we care only about specific headers
// we can skip the rest
if (!strcmp("content-length", key)) {
if (parse_ctx->content_length == TRANSFER_ENCODING_CHUNKED) {
netdata_log_error("Content-length and transfer-encoding: chunked headers are mutually exclusive");
return 1;
}
if (parse_ctx->content_length != -1) {
netdata_log_error("Duplicate content-length header");
return 1;
}
parse_ctx->content_length = atoi(val);
if (parse_ctx->content_length < 0) {
netdata_log_error("Invalid content-length %d", parse_ctx->content_length);
return 1;
}
return 0;
}
if (!strcmp("transfer-encoding", key)) {
if (!strcmp("chunked", val)) {
if (parse_ctx->content_length != -1) {
netdata_log_error("Content-length and transfer-encoding: chunked headers are mutually exclusive");
return 1;
}
parse_ctx->content_length = TRANSFER_ENCODING_CHUNKED;
}
return 0;
}
return 0;
}
static int parse_http_hdr(rbuf_t buf, http_parse_ctx *parse_ctx)
{
int idx, idx_end;
char buf_key[HTTP_HDR_BUFFER_SIZE];
char buf_val[HTTP_HDR_BUFFER_SIZE];
char *ptr = buf_key;
if (!rbuf_find_bytes(buf, HTTP_LINE_TERM, strlen(HTTP_LINE_TERM), &idx_end)) {
netdata_log_error("CRLF expected");
return 1;
}
char *separator = rbuf_find_bytes(buf, HTTP_KEYVAL_SEPARATOR, strlen(HTTP_KEYVAL_SEPARATOR), &idx);
if (!separator) {
netdata_log_error("Missing Key/Value separator");
return 1;
}
if (idx >= HTTP_HDR_BUFFER_SIZE) {
netdata_log_error("Key name is too long");
return 1;
}
rbuf_pop(buf, buf_key, idx);
buf_key[idx] = 0;
rbuf_bump_tail(buf, strlen(HTTP_KEYVAL_SEPARATOR));
idx_end -= strlen(HTTP_KEYVAL_SEPARATOR) + idx;
if (idx_end >= HTTP_HDR_BUFFER_SIZE) {
netdata_log_error("Value of key \"%s\" too long", buf_key);
return 1;
}
rbuf_pop(buf, buf_val, idx_end);
buf_val[idx_end] = 0;
for (ptr = buf_key; *ptr; ptr++)
*ptr = tolower(*ptr);
if (process_http_hdr(parse_ctx, buf_key, buf_val))
return 1;
return 0;
}
static inline void chunked_response_buffer_grow_by(http_parse_ctx *parse_ctx, size_t size)
{
if (unlikely(parse_ctx->chunked_response_size == 0)) {
parse_ctx->chunked_response = mallocz(size);
parse_ctx->chunked_response_size = size;
return;
}
parse_ctx->chunked_response = reallocz((void *)parse_ctx->chunked_response, parse_ctx->chunked_response_size + size);
parse_ctx->chunked_response_size += size;
}
static in