// Written by retoor@molodetz.nl
// Improved version with better reliability and chunked content support
// MIT License
#ifndef R_HTTP_H
#define R_HTTP_H
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <json-c/json.h>
#include <stdbool.h>
#include "url.h"
#define INITIAL_BUFFER_SIZE (1024 * 1024)
#define MAX_HEADER_SIZE (8 * 1024)
void init_openssl() {
SSL_load_error_strings();
OpenSSL_add_ssl_algorithms();
}
void cleanup_openssl() {
EVP_cleanup();
}
SSL_CTX *create_context() {
const SSL_METHOD *method = TLS_client_method();
SSL_CTX *ctx = SSL_CTX_new(method);
if (!ctx) {
perror("Unable to create SSL context");
ERR_print_errors_fp(stderr);
return NULL;
}
SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);
SSL_CTX_load_verify_locations(ctx, "/etc/ssl/certs/ca-certificates.crt", NULL);
return ctx;
}
int create_socket(const char *hostname, int port) {
struct hostent *host;
struct sockaddr_in addr;
host = gethostbyname(hostname);
if (!host) {
perror("Unable to resolve host");
return -1;
}
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
perror("Unable to create socket");
return -1;
}
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = *(long *)(host->h_addr);
if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
perror("Unable to connect to host");
close(sock);
return -1;
}
return sock;
}
char *read_until_ssl(SSL *ssl, const char *until, size_t max_size) {
char *data = (char *)malloc(max_size);
if (!data) return NULL;
size_t index = 0;
size_t until_len = strlen(until);
while (index < max_size - 1) {
char c;
int bytes = SSL_read(ssl, &c, 1);
if (bytes <= 0) {
free(data);
return NULL;
}
data[index++] = c;
data[index] = '\0';
if (index >= until_len &&
memcmp(data + index - until_len, until, until_len) == 0) {
return data;
}
}
free(data);
return NULL;
}
char *read_until(int sock, const char *until, size_t max_size) {
char *data = (char *)malloc(max_size);
if (!data) return NULL;
size_t index = 0;
size_t until_len = strlen(until);
while (index < max_size - 1) {
char c;
ssize_t bytes = recv(sock, &c, 1, 0);
if (bytes <= 0) {
free(data);
return NULL;
}
data[index++] = c;
data[index] = '\0';
if (index >= until_len &&
memcmp(data + index - until_len, until, until_len) == 0) {
return data;
}
}
free(data);
return NULL;
}
size_t hex_to_int(const char *hex) {
size_t result = 0;
while (*hex && *hex != '\r' && *hex != '\n') {
char c = *hex++;
if (c >= '0' && c <= '9') {
result = result * 16 + (c - '0');
} else if (c >= 'a' && c <= 'f') {
result = result * 16 + (c - 'a' + 10);
} else if (c >= 'A' && c <= 'F') {
result = result * 16 + (c - 'A' + 10);
}
}
return result;
}
bool http_has_header(const char *http_headers, const char *http_header_name) {
char search_for[strlen(http_header_name) + 3];
snprintf(search_for, sizeof(search_for), "%s: ", http_header_name);
return strstr(http_headers, search_for) != NULL;
}
char *http_header_get_str(const char *http_headers, const char *http_header_name) {
char search_for[strlen(http_header_name) + 3];
snprintf(search_for, sizeof(search_for), "%s: ", http_header_name);
const char *header = strstr(http_headers, search_for);
if (!header) return NULL;
header += strlen(search_for);
const char *end = strstr(header, "\r\n");
if (!end) return NULL;
size_t value_len = end - header;
char *result = (char *)malloc(value_len + 1);
if (!result) return NULL;
memcpy(result, header, value_len);
result[value_len] = '\0';
return result;
}
long http_header_get_long(const char *http_headers, const char *http_header_name) {
char *str_value = http_header_get_str(http_headers, http_header_name);
if (!str_value) return 0;
long long_value = atol(str_value);
free(str_value);
return long_value;
}
static char *read_chunked_body_ssl(SSL *ssl) {
size_t total_size = 0;
size_t buffer_capacity = INITIAL_BUFFER_SIZE;
char *buffer = (char *)malloc(buffer_capacity);
if (!buffer) return NULL;
while (1) {
// Read chunk size line
char *chunk_header = read_until_ssl(ssl, "\r\n", 256);
if (!chunk_header) {
free(buffer);
return NULL;
}
size_t chunk_size = hex_to_int(chunk_header);
free(chunk_header);
if (chunk_size == 0) {
// Read trailing \r\n after last chunk
char trailing[3];
SSL_read(ssl, trailing, 2);
break;
}
// Resize buffer if needed
if (total_size + chunk_size + 1 > buffer_capacity) {
buffer_capacity = total_size + chunk_size + INITIAL_BUFFER_SIZE;
char *new_buffer = (char *)realloc(buffer, buffer_capacity);
if (!new_buffer) {
free(buffer);
return NULL;
}
buffer = new_buffer;
}
// Read chunk data
size_t bytes_read = 0;
while (bytes_read < chunk_size) {
int n = SSL_read(ssl, buffer + total_size + bytes_read,
chunk_size - bytes_read);
if (n <= 0) {
free(buffer);
return NULL;
}
bytes_read += n;
}
total_size += chunk_size;
// Read trailing \r\n after chunk data
char trailing[3];
SSL_read(ssl, trailing, 2);
}
buffer[total_size] = '\0';
return buffer;
}
static char *read_chunked_body(int sock) {
size_t total_size = 0;
size_t buffer_capacity = INITIAL_BUFFER_SIZE;
char *buffer = (char *)malloc(buffer_capacity);
if (!buffer) return NULL;
while (1) {
// Read chunk size line
char *chunk_header = read_until(sock, "\r\n", 256);
if (!chunk_header) {
free(buffer);
return NULL;
}
size_t chunk_size = hex_to_int(chunk_header);
free(chunk_header);
if (chunk_size == 0) {
// Read trailing \r\n after last chunk
char trailing[3];
recv(sock, trailing, 2, 0);
break;
}
// Resize buffer if needed
if (total_size + chunk_size + 1 > buffer_capacity) {
buffer_capacity = total_size + chunk_size + INITIAL_BUFFER_SIZE;
char *new_buffer = (char *)realloc(buffer, buffer_capacity);
if (!new_buffer) {
free(buffer);
return NULL;
}
buffer = new_buffer;
}
// Read chunk data
size_t bytes_read = 0;
while (bytes_read < chunk_size) {
ssize_t n = recv(sock, buffer + total_size + bytes_read,
chunk_size - bytes_read, 0);
if (n <= 0) {
free(buffer);
return NULL;
}
bytes_read += n;
}
total_size += chunk_size;
// Read trailing \r\n after chunk data
char trailing[3];
recv(sock, trailing, 2, 0);
}
buffer[total_size] = '\0';
return buffer;
}
char *https_post(char *url, char *data) {
url_t parsed_url;
parse_url(url, &parsed_url);
init_openssl();
SSL_CTX *ctx = create_context();
if (!ctx) return NULL;
int sock = create_socket(parsed_url.hostname, 443);
if (sock < 0) {
SSL_CTX_free(ctx);
cleanup_openssl();
return NULL;
}
SSL *ssl = SSL_new(ctx);
SSL_set_connect_state(ssl);
SSL_set_tlsext_host_name(ssl, parsed_url.hostname);
SSL_set_fd(ssl, sock);
char *result = NULL;
if (SSL_connect(ssl) <= 0) {
ERR_print_errors_fp(stderr);
} else {
size_t data_len = strlen(data);
size_t request_size = data_len + 1024;
char *request = (char *)malloc(request_size);
snprintf(request, request_size,
"POST %s HTTP/1.1\r\n"
"Host: %s\r\n"
"Content-Length: %zu\r\n"
"Content-Type: application/json\r\n"
"Connection: close\r\n"
"\r\n"
"%s",
parsed_url.path, parsed_url.hostname, data_len,
data);
SSL_write(ssl, request, strlen(request));
free(request);
char *headers = read_until_ssl(ssl, "\r\n\r\n", MAX_HEADER_SIZE);
if (headers) {
long content_length = http_header_get_long(headers, "Content-Length");
if (content_length > 0) {
result = (char *)malloc(content_length + 1);
if (result) {
size_t bytes_read = 0;
while (bytes_read < (size_t)content_length) {
int n = SSL_read(ssl, result + bytes_read,
content_length - bytes_read);
if (n <= 0) {
free(result);
result = NULL;
break;
}
bytes_read += n;
}
if (result) {
result[content_length] = '\0';
}
}
} else if (http_has_header(headers, "Transfer-Encoding")) {
result = read_chunked_body_ssl(ssl);
}
free(headers);
}
}
SSL_free(ssl);
close(sock);
SSL_CTX_free(ctx);
cleanup_openssl();
return result;
}
char *https_get(char *url) {
url_t parsed_url;
parse_url(url, &parsed_url);
init_openssl();
SSL_CTX *ctx = create_context();
if (!ctx) return NULL;
int sock = create_socket(parsed_url.hostname, 443);
if (sock < 0) {
SSL_CTX_free(ctx);
cleanup_openssl();
return NULL;
}
SSL *ssl = SSL_new(ctx);
SSL_set_connect_state(ssl);
SSL_set_tlsext_host_name(ssl, parsed_url.hostname);
SSL_set_fd(ssl, sock);
char *result = NULL;
if (SSL_connect(ssl) <= 0) {
ERR_print_errors_fp(stderr);
} else {
char request[2048];
snprintf(request, sizeof(request),
"GET %s HTTP/1.1\r\n"
"Host: %s\r\n"
"Connection: close\r\n"
"\r\n",
parsed_url.path, parsed_url.hostname);
SSL_write(ssl, request, strlen(request));
char *headers = read_until_ssl(ssl, "\r\n\r\n", MAX_HEADER_SIZE);
if (headers) {
long content_length = http_header_get_long(headers, "Content-Length");
if (content_length > 0) {
result = (char *)malloc(content_length + 1);
if (result) {
size_t bytes_read = 0;
while (bytes_read < (size_t)content_length) {
int n = SSL_read(ssl, result + bytes_read,
content_length - bytes_read);
if (n <= 0) {
free(result);
result = NULL;
break;
}
bytes_read += n;
}
if (result) {
result[content_length] = '\0';
}
}
} else if (http_has_header(headers, "Transfer-Encoding")) {
result = read_chunked_body_ssl(ssl);
}
free(headers);
}
}
SSL_free(ssl);
close(sock);
SSL_CTX_free(ctx);
cleanup_openssl();
return result;
}
char *http_post(char *url, char *data) {
url_t parsed_url;
parse_url(url, &parsed_url);
int port = parsed_url.port ? atoi(parsed_url.port) : 80;
int sock = create_socket(parsed_url.hostname, port);
if (sock < 0) return NULL;
char *result = NULL;
size_t data_len = strlen(data);
size_t request_size = data_len + 1024;
char *request = (char *)malloc(request_size);
snprintf(request, request_size,
"POST %s HTTP/1.1\r\n"
"Host: %s\r\n"
"Content-Length: %zu\r\n"
"Content-Type: application/json\r\n"
"Connection: close\r\n"
"\r\n"
"%s",
parsed_url.path, parsed_url.hostname, data_len,
data);
send(sock, request, strlen(request), 0);
free(request);
char *headers = read_until(sock, "\r\n\r\n", MAX_HEADER_SIZE);
if (headers) {
long content_length = http_header_get_long(headers, "Content-Length");
if (content_length > 0) {
result = (char *)malloc(content_length + 1);
if (result) {
size_t bytes_read = 0;
while (bytes_read < (size_t)content_length) {
ssize_t n = recv(sock, result + bytes_read,
content_length - bytes_read, 0);
if (n <= 0) {
free(result);
result = NULL;
break;
}
bytes_read += n;
}
if (result) {
result[content_length] = '\0';
}
}
} else if (http_has_header(headers, "Transfer-Encoding")) {
result = read_chunked_body(sock);
}
free(headers);
}
close(sock);
return result;
}
char *http_get(char *url) {
url_t parsed_url;
parse_url(url, &parsed_url);
int port = parsed_url.port ? atoi(parsed_url.port) : 80;
int sock = create_socket(parsed_url.hostname, port);
if (sock < 0) return NULL;
char *result = NULL;
char request[2048];
snprintf(request, sizeof(request),
"GET %s HTTP/1.1\r\n"
"Host: %s\r\n"
"Connection: close\r\n"
"\r\n",
parsed_url.path, parsed_url.hostname);
send(sock, request, strlen(request), 0);
char *headers = read_until(sock, "\r\n\r\n", MAX_HEADER_SIZE);
if (headers) {
long content_length = http_header_get_long(headers, "Content-Length");
if (content_length > 0) {
result = (char *)malloc(content_length + 1);
if (result) {
size_t bytes_read = 0;
while (bytes_read < (size_t)content_length) {
ssize_t n = recv(sock, result + bytes_read,
content_length - bytes_read, 0);
if (n <= 0) {
free(result);
result = NULL;
break;
}
bytes_read += n;
}
if (result) {
result[content_length] = '\0';
}
}
} else if (http_has_header(headers, "Transfer-Encoding")) {
result = read_chunked_body(sock);
}
free(headers);
}
close(sock);
return result;
}
#endif