// 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 #include #include #include #include #include #include #include #include #include #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