Repository: pgmoneta/pgmoneta
Pull request #620: Remove cURL dependency
Author: login: bassamadnan
Labels: name: feature
description: New feature
Created: 2025-05-04T06:22:37Z | Merged: 2025-05-11T21:00:34Z

-- This PR addresses #593 by creating home-grown http suite --
This is only a draft PR, some changes made here are temporary and cosmetic mainly for development. Final PR will be a squashed commit. @jesperpedersen The s3 functionality works

To build:
```
cd build && CFLAGS="-w" cmake -DCMAKE_C_COMPILER=gcc -DCMAKE_BUILD_TYPE=Debug .. && make
```
Ignoring warnings for now since migration isnt entirely complete


Diff:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d0e4a2ff2..731955911 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -31,7 +31,7 @@ jobs:
           git gcc clang clang-tools cmake make \
           libev4 libev-dev libssl-dev libsystemd-dev \
           zlib1g zlib1g-dev libzstd-dev liblz4-dev \
-          libssh-dev libcurl4-openssl-dev libatomic1 \
+          libssh-dev libatomic1 \
           python3-docutils libbz2-dev libarchive-dev \
           bzip2 net-tools check graphviz doxygen \
           pandoc texlive texlive-latex-extra \
@@ -254,7 +254,6 @@ jobs:
               libarchive \
               check \
               py311-docutils \
-              curl \
               doxygen \
               hs-pandoc \
               texlive-base \
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 55246dd05..93c4d9387 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -218,13 +218,6 @@ else ()
   message(FATAL_ERROR "libssh needed")
 endif()
 
-find_package(Libcurl)
-if (LIBCURL_FOUND)
-  message(STATUS "libcurl found")
-else ()
-  message(FATAL_ERROR "libcurl needed")
-endif()
-
 find_package(THREAD)
 if (THREAD_FOUND)
   message(STATUS "pthread found")
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 6867d1163..ce857859d 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -34,7 +34,7 @@ Don't forget to indicate your pgmoneta version.
 You can use the follow command, if you are using a [Fedora](https://getfedora.org/) based platform:
 
 ```
-dnf install git gcc clang clang-analyzer cmake make libev libev-devel openssl openssl-devel systemd systemd-devel zlib zlib-devel libzstd libzstd-devel lz4 lz4-devel libssh libssh-devel python3-docutils libatomic libcurl libcurl-devel bzip2 bzip2-devel libarchive libarchive-devel libasan libasan-static
+dnf install git gcc clang clang-analyzer cmake make libev libev-devel openssl openssl-devel systemd systemd-devel zlib zlib-devel libzstd libzstd-devel lz4 lz4-devel libssh libssh-devel python3-docutils libatomic bzip2 bzip2-devel libarchive libarchive-devel libasan libasan-static
 ```
 
 in order to get the necessary dependencies.
diff --git a/README.md b/README.md
index f4974bff9..6d72e91f3 100644
--- a/README.md
+++ b/README.md
@@ -63,13 +63,12 @@ See [Architecture](./doc/ARCHITECTURE.md) for the architecture of **pgmoneta**.
 * [systemd](https://www.freedesktop.org/wiki/Software/systemd/)
 * [rst2man](https://docutils.sourceforge.io/)
 * [libssh](https://www.libssh.org/)
-* [libcurl](https://curl.se/libcurl/)
 * [libarchive](http://www.libarchive.org/)
 * [pandoc](https://pandoc.org/)
 * [texlive](https://www.tug.org/texlive/)
 
 ```sh
-dnf install git gcc clang clang-analyzer cmake make libev libev-devel openssl openssl-devel systemd systemd-devel zlib zlib-devel libzstd libzstd-devel lz4 lz4-devel libssh libssh-devel libcurl libcurl-devel python3-docutils libatomic bzip2 bzip2-devel libarchive libarchive-devel libasan libasan-static
+dnf install git gcc clang clang-analyzer cmake make libev libev-devel openssl openssl-devel systemd systemd-devel zlib zlib-devel libzstd libzstd-devel lz4 lz4-devel libssh libssh-devel python3-docutils libatomic bzip2 bzip2-devel libarchive libarchive-devel libasan libasan-static
 ```
 
 Alternative [gcc](https://gcc.gnu.org) can be used.
diff --git a/cmake/FindLibcurl.cmake b/cmake/FindLibcurl.cmake
deleted file mode 100644
index 131dad1ea..000000000
--- a/cmake/FindLibcurl.cmake
+++ /dev/null
@@ -1,23 +0,0 @@
-#
-# Libcurl support
-#
-
-find_path(CURL_INCLUDE_DIR
-  NAMES
-    curl/curl.h
-)
-find_library(CURL_LIBRARY
-  NAMES
-    curl
-)
-
-include(FindPackageHandleStandardArgs)
-find_package_handle_standard_args(Libcurl DEFAULT_MSG
-                                  CURL_LIBRARY CURL_INCLUDE_DIR)
-
-if(LIBCURL_FOUND)
-  set(CURL_LIBRARIES ${CURL_LIBRARY})
-  set(CURL_INCLUDE_DIRS ${CURL_INCLUDE_DIR})
-endif()
-
-mark_as_advanced(CURL_LIBRARY CURL_INCLUDE_DIR)
\ No newline at end of file
diff --git a/contrib/docker/Dockerfile.alpine b/contrib/docker/Dockerfile.alpine
index 604390723..10defada4 100644
--- a/contrib/docker/Dockerfile.alpine
+++ b/contrib/docker/Dockerfile.alpine
@@ -53,8 +53,6 @@ RUN apk add --no-cache \
     yaml-dev \
     libarchive \
     libarchive-dev \
-    libcurl \
-    curl-dev \
     libssh \
     libssh-dev 
 
@@ -88,8 +86,7 @@ RUN echo "http://dl-cdn.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositori
     zstd \
     lz4-libs \
     libarchive \
-    libssh \
-    libcurl 
+    libssh
 
 RUN adduser -D -s /bin/sh pgmoneta
 
diff --git a/contrib/docker/Dockerfile.rocky9 b/contrib/docker/Dockerfile.rocky9
index dbea25e34..582372a28 100644
--- a/contrib/docker/Dockerfile.rocky9
+++ b/contrib/docker/Dockerfile.rocky9
@@ -60,7 +60,6 @@ RUN rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.
         libarchive \
         libarchive-devel \
         libssh-devel \
-        libcurl-devel \
         libasan \
         libasan-static \
         yaml-cpp-devel && \
diff --git a/doc/DEVELOPERS.md b/doc/DEVELOPERS.md
index 614632d1e..cde5626c3 100644
--- a/doc/DEVELOPERS.md
+++ b/doc/DEVELOPERS.md
@@ -45,7 +45,7 @@ This will install PostgreSQL 17.
 #### Basic dependencies
 
 ``` sh
-dnf install git gcc clang clang-analyzer cmake make libev libev-devel openssl openssl-devel systemd systemd-devel zlib zlib-devel libzstd libzstd-devel lz4 lz4-devel libssh libssh-devel libcurl libcurl-devel python3-docutils libatomic bzip2 bzip2-devel libarchive libarchive-devel libasan libasan-static
+dnf install git gcc clang clang-analyzer cmake make libev libev-devel openssl openssl-devel systemd systemd-devel zlib zlib-devel libzstd libzstd-devel lz4 lz4-devel libssh libssh-devel python3-docutils libatomic bzip2 bzip2-devel libarchive libarchive-devel libasan libasan-static
 ```
 
 #### Generate user and developer guide
diff --git a/doc/DISTRIBUTIONS.md b/doc/DISTRIBUTIONS.md
index 7c7e80ef3..32f0174de 100644
--- a/doc/DISTRIBUTIONS.md
+++ b/doc/DISTRIBUTIONS.md
@@ -15,14 +15,13 @@
 * [systemd](https://www.freedesktop.org/wiki/Software/systemd/)
 * [rst2man](https://docutils.sourceforge.io/)
 * [libssh](https://www.libssh.org/)
-* [libcurl](https://curl.se/libcurl/)
 * [libarchive](http://www.libarchive.org/)
 * [pandoc](https://pandoc.org/)
 * [texlive](https://www.tug.org/texlive/)
 
 On Fedora, these can be installed using `dnf` or `yum`:
 ```
-dnf install git gcc clang clang-analyzer cmake make libev libev-devel openssl openssl-devel systemd systemd-devel zlib zlib-devel libzstd libzstd-devel lz4 lz4-devel libssh libssh-devel libcurl libcurl-devel python3-docutils libatomic bzip2 bzip2-devel libarchive libarchive-devel libasan libasan-static pandoc texlive-scheme-basic 'tex(footnote.sty)'
+dnf install git gcc clang clang-analyzer cmake make libev libev-devel openssl openssl-devel systemd systemd-devel zlib zlib-devel libzstd libzstd-devel lz4 lz4-devel libssh libssh-devel python3-docutils libatomic bzip2 bzip2-devel libarchive libarchive-devel libasan libasan-static pandoc texlive-scheme-basic 'tex(footnote.sty)'
 ```
 On Rocky, before you install the required packages, some additional repositories, CodeReady Builder and EPEL in this case, need to be enabled or installed first.
 ```
@@ -73,7 +72,7 @@ On FreeBSD, `pkg` is used instead of `dnf` or `yum`.
 
 Use `pkg install <package name>` to install the following packages
 ```
-git gcc cmake libev openssl libssh zlib-ng zstd liblz4 bzip2 curl py39-docutils libarchive
+git gcc cmake libev openssl libssh zlib-ng zstd liblz4 bzip2 py39-docutils libarchive
 ```
 
 ## Compile
diff --git a/doc/manual/02-installation.md b/doc/manual/02-installation.md
index 944e42fee..f26672749 100644
--- a/doc/manual/02-installation.md
+++ b/doc/manual/02-installation.md
@@ -61,7 +61,6 @@ We recommend using Fedora to test and run [**pgmoneta**][pgmoneta], but other Li
 * [systemd](https://www.freedesktop.org/wiki/Software/systemd/)
 * [rst2man](https://docutils.sourceforge.io/)
 * [libssh](https://www.libssh.org/)
-* [libcurl](https://curl.se/libcurl/)
 * [libarchive](http://www.libarchive.org/)
 
 ```sh
@@ -70,7 +69,6 @@ dnf install git gcc clang clang-analyzer cmake make libev libev-devel \
             systemd systemd-devel zlib zlib-devel \
             libzstd libzstd-devel \
             lz4 lz4-devel libssh libssh-devel \
-            libcurl libcurl-devel \
             python3-docutils libatomic \
             bzip2 bzip2-devel \
             libarchive libarchive-devel
@@ -121,7 +119,7 @@ On FreeBSD, `pkg` is used instead of `dnf` or `yum`.
 Use `pkg install <package name>` to install the following packages
 
 ``` sh
-git gcc cmake libev openssl libssh zlib-ng zstd liblz4 bzip2 curl \
+git gcc cmake libev openssl libssh zlib-ng zstd liblz4 bzip2 \
     py39-docutils libarchive
 ```
 
diff --git a/doc/manual/99-references.md b/doc/manual/99-references.md
index a64e84aed..09f5312c9 100644
--- a/doc/manual/99-references.md
+++ b/doc/manual/99-references.md
@@ -20,7 +20,6 @@
   [systemd]: https://www.freedesktop.org/wiki/Software/systemd/
   [rst2man]: https://docutils.sourceforge.io/
   [libssh]: https://www.libssh.org/
-  [libcurl]: https://curl.se/libcurl/
   [libarchive]: http://www.libarchive.org/
   [pandoc]: https://pandoc.org/
   [pandoc_latex_template]: https://github.com/Wandmalfarbe/pandoc-latex-template
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index d0676ff67..5bb301d1c 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -32,7 +32,6 @@ if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
     ${LIBEV_INCLUDE_DIRS}
     ${OPENSSL_INCLUDE_DIR}
     ${LIBSSH_INCLUDE_DIRS}
-    ${CURL_INCLUDE_DIRS}
     ${LibArchive_INCLUDE_DIRS}
     ${THREAD_INCLUDE_DIRS}
   )
@@ -59,7 +58,6 @@ if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
     ${OPENSSL_CRYPTO_LIBRARY}
     ${OPENSSL_SSL_LIBRARY}
     ${LIBSSH_LIBRARIES}
-    ${CURL_LIBRARIES}
     ${LIBATOMIC_LIBRARY}
     ${LibArchive_LIBRARY}
     ${THREAD_LIBRARY}
@@ -94,7 +92,6 @@ elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
     ${LIBEV_INCLUDE_DIRS}
     ${OPENSSL_INCLUDE_DIR}
     ${LIBSSH_INCLUDE_DIRS}
-    ${CURL_INCLUDE_DIRS}
     ${LibArchive_INCLUDE_DIRS}
     ${THREAD_INCLUDE_DIRS}
   )
@@ -111,7 +108,6 @@ elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
     ${OPENSSL_CRYPTO_LIBRARY}
     ${OPENSSL_SSL_LIBRARY}
     ${LIBSSH_LIBRARIES}
-    ${CURL_LIBRARIES}
     ${LIBATOMIC_LIBRARY}
     ${LibArchive_LIBRARY}
     ${THREAD_LIBRARY}
@@ -136,7 +132,6 @@ else()
     ${LIBEV_INCLUDE_DIRS}
     ${OPENSSL_INCLUDE_DIR}
     ${LIBSSH_INCLUDE_DIRS}
-    ${CURL_INCLUDE_DIRS}
     ${LibArchive_INCLUDE_DIRS}
     ${THREAD_INCLUDE_DIRS}
   )
@@ -153,7 +148,6 @@ else()
     ${OPENSSL_CRYPTO_LIBRARY}
     ${OPENSSL_SSL_LIBRARY}
     ${LIBSSH_LIBRARIES}
-    ${CURL_LIBRARIES}
     ${LibArchive_LIBRARIES}
     ${THREAD_LIBRARIES}
   )
diff --git a/src/include/http.h b/src/include/http.h
index f3a918972..bef0384c6 100644
--- a/src/include/http.h
+++ b/src/include/http.h
@@ -38,49 +38,112 @@ extern "C" {
 #include <stdbool.h>
 #include <stdint.h>
 #include <stdio.h>
-#include <curl/curl.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 
-#define HTTP_GET 0
-#define HTTP_PUT 1
+/* HTTP method definitions */
+#define PGMONETA_HTTP_GET  0
+#define PGMONETA_HTTP_POST 1
+#define PGMONETA_HTTP_PUT  2
+
+/** @struct http
+ * Defines a HTTP interaction
+ */
+struct http
+{
+   int endpoint;           /**< The endpoint */
+   int socket;             /**< The socket descriptor */
+   char* body;             /**< The HTTP response body */
+   char* headers;          /**< The HTTP response headers */
+   char* request_headers;  /**< The HTTP request headers */
+   SSL* ssl;               /**< The SSL connection (NULL for non-secure) */
+};
+
+/**
+ * Connect to an HTTP/HTTPS server
+ * @param hostname The host to connect to
+ * @param port The port number
+ * @param secure Use SSL if true
+ * @param result The resulting HTTP structure
+ * @return 0 upon success, otherwise 1
+ */
+int
+pgmoneta_http_connect(char* hostname, int port, bool secure, struct http** result);
 
 /**
- * Add a header
- * @param chunk A linked list of strings
- * @param header The header
- * @param value The header's value
- * @return A new list pointer
+ * Disconnect and clean up HTTP resources
+ * @param http The HTTP structure
+ * @return 0 upon success, otherwise 1
  */
-struct curl_slist*
-pgmoneta_http_add_header(struct curl_slist* chunk, char* header, char* value);
+int
+pgmoneta_http_disconnect(struct http* http);
+
+/**
+ * Add a header to the HTTP request
+ * @param http The HTTP structure
+ * @param name The header name
+ * @param value The header value
+ * @return 0 upon success, otherwise 1
+ */
+int
+pgmoneta_http_add_header(struct http* http, char* name, char* value);
+
+/**
+ * Read response data directly from a socket
+ * @param ssl SSL connection (can be NULL for non-secure connections)
+ * @param socket The socket to read from
+ * @param response_text Pointer to store the response text
+ * @return MESSAGE_STATUS_OK on success, MESSAGE_STATUS_ERROR otherwise
+ */
+int
+pgmoneta_http_read(SSL* ssl, int socket, char** response_text);
+
+/**
+ * Perform HTTP GET request
+ * @param http The HTTP structure
+ * @param hostname The hostname for the Host header
+ * @param path The path for the request
+ * @return 0 upon success, otherwise 1
+ */
+int
+pgmoneta_http_get(struct http* http, char* hostname, char* path);
 
 /**
- * set HTTP headers
- * @param handle A CURL easy handle
- * @param headers A pointer to a linked list of HTTP headers
+ * Perform HTTP POST request
+ * @param http The HTTP structure
+ * @param hostname The hostname for the Host header
+ * @param path The path for the request
+ * @param data The data to send
+ * @param length The length of the data
  * @return 0 upon success, otherwise 1
  */
 int
-pgmoneta_http_set_header_option(CURL* handle, struct curl_slist* chunk);
+pgmoneta_http_post(struct http* http, char* hostname, char* path, char* data, size_t length);
 
 /**
- * set HTTP request
- * @param handle A CURL easy handle
- * @param request_type A http request type
+ * Perform HTTP PUT request
+ * @param http The HTTP structure
+ * @param hostname The hostname for the Host header
+ * @param path The path for the request
+ * @param data The data to upload
+ * @param length The length of the data
  * @return 0 upon success, otherwise 1
  */
 int
-pgmoneta_http_set_request_option(CURL* handle, bool request_type);
+pgmoneta_http_put(struct http* http, char* hostname, char* path, const void* data, size_t length);
 
 /**
- * set the URL
- * @param handle A CURL easy handle
- * @param url A URL for the transfer
+ * Perform HTTP PUT request with a file
+ * @param http The HTTP structure
+ * @param hostname The hostname for the Host header
+ * @param path The path for the request
+ * @param file The file to upload
+ * @param file_size The size of the file
+ * @param content_type The content type of the file (can be NULL for default)
  * @return 0 upon success, otherwise 1
  */
 int
-pgmoneta_http_set_url_option(CURL* handle, char* url);
+pgmoneta_http_put_file(struct http* http, char* hostname, char* path, FILE* file, size_t file_size, char* content_type);
 
 #ifdef __cplusplus
 }
diff --git a/src/include/utils.h b/src/include/utils.h
index dd8ab62dc..89c6ccfe0 100644
--- a/src/include/utils.h
+++ b/src/include/utils.h
@@ -1349,6 +1349,15 @@ pgmoneta_backtrace(void);
 int
 pgmoneta_os_kernel_version(char** os, int* kernel_major, int* kernel_minor, int* kernel_patch);
 
+/**
+ * Is the string a number ?
+ * @param str The string
+ * @param base The base (10 or 16)
+ * @return True if number, otherwise false
+ */
+bool
+pgmoneta_is_number(char* str, int base);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/src/libpgmoneta/http.c b/src/libpgmoneta/http.c
index 928704c46..152467573 100644
--- a/src/libpgmoneta/http.c
+++ b/src/libpgmoneta/http.c
@@ -29,99 +29,818 @@
 /* pgmoneta */
 #include <pgmoneta.h>
 #include <http.h>
+#include <logging.h>
+#include <network.h>
+#include <security.h>
 #include <utils.h>
 
-struct curl_slist*
-pgmoneta_http_add_header(struct curl_slist* chunk, char* header, char* value)
+/* system */
+#include <errno.h>
+#include <unistd.h>
+#include <openssl/err.h>
+
+static int http_build_header(int method, char* path, char** request);
+static int http_extract_headers_body(char* response, struct http* http);
+
+int
+pgmoneta_http_add_header(struct http* http, char* name, char* value)
 {
-   char* h = NULL;
+   char* tmp;
 
-   h = pgmoneta_append(h, header);
-   h = pgmoneta_append(h, ": ");
-   h = pgmoneta_append(h, value);
+   tmp = pgmoneta_append(http->request_headers, name);
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   chunk = curl_slist_append(chunk, h);
+   tmp = pgmoneta_append(http->request_headers, ": ");
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   free(h);
+   tmp = pgmoneta_append(http->request_headers, value);
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
+
+   tmp = pgmoneta_append(http->request_headers, "\r\n");
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
+
+   return 0;
+}
+
+static int
+http_build_header(int method, char* path, char** request)
+{
+   char* r = NULL;
+   *request = NULL;
+
+   if (method == PGMONETA_HTTP_GET)
+   {
+      r = pgmoneta_append(r, "GET ");
+   }
+   else if (method == PGMONETA_HTTP_POST)
+   {
+      r = pgmoneta_append(r, "POST ");
+   }
+   else if (method == PGMONETA_HTTP_PUT)
+   {
+      r = pgmoneta_append(r, "PUT ");
+   }
+   else
+   {
+      pgmoneta_log_error("Invalid HTTP method: %d", method);
+      return 1;
+   }
+
+   r = pgmoneta_append(r, path);
+   r = pgmoneta_append(r, " HTTP/1.1\r\n");
+
+   *request = r;
+
+   return 0;
+}
+
+static int
+http_extract_headers_body(char* response, struct http* http)
+{
+   bool header = true;
+   char* p = NULL;
+   char* response_copy = NULL;
 
-   return chunk;
+   if (response == NULL)
+   {
+      pgmoneta_log_error("Response is NULL");
+      return 1;
+   }
+
+   response_copy = strdup(response);
+   if (response_copy == NULL)
+   {
+      pgmoneta_log_error("Failed to duplicate response string");
+      return 1;
+   }
+
+   p = strtok(response_copy, "\n");
+   while (p != NULL)
+   {
+      if (*p == '\r')
+      {
+         header = false;
+      }
+      else
+      {
+         if (!pgmoneta_is_number(p, 16))
+         {
+            if (header)
+            {
+               http->headers = pgmoneta_append(http->headers, p);
+               http->headers = pgmoneta_append_char(http->headers, '\n');
+            }
+            else
+            {
+               http->body = pgmoneta_append(http->body, p);
+               http->body = pgmoneta_append_char(http->body, '\n');
+            }
+         }
+      }
+
+      p = strtok(NULL, "\n");
+   }
+
+   free(response_copy);
+   return 0;
 }
 
 int
-pgmoneta_http_set_header_option(CURL* handle, struct curl_slist* chunk)
+pgmoneta_http_get(struct http* http, char* hostname, char* path)
 {
-   CURLcode res;
+   struct message* msg_request = NULL;
+   int error = 0;
+   int status;
+   char* request = NULL;
+   char* full_request = NULL;
+   char* response = NULL;
+   char* user_agent = NULL;
+   char* endpoint = path ? path : "/get";
 
-   if (handle == NULL)
+   pgmoneta_log_trace("Starting pgmoneta_http_get");
+   if (http_build_header(PGMONETA_HTTP_GET, endpoint, &request))
    {
+      pgmoneta_log_error("Failed to build HTTP header");
       goto error;
    }
 
-   res = curl_easy_setopt(handle, CURLOPT_HTTPHEADER, chunk);
+   pgmoneta_http_add_header(http, "Host", hostname);
+   user_agent = pgmoneta_append(user_agent, "pgmoneta/");
+   user_agent = pgmoneta_append(user_agent, VERSION);
+   pgmoneta_http_add_header(http, "User-Agent", user_agent);
+   pgmoneta_http_add_header(http, "Accept", "text/*");
+   pgmoneta_http_add_header(http, "Connection", "close");
+
+   full_request = pgmoneta_append(NULL, request);
+   full_request = pgmoneta_append(full_request, http->request_headers);
+   full_request = pgmoneta_append(full_request, "\r\n");
+
+   msg_request = (struct message*)malloc(sizeof(struct message));
+   if (msg_request == NULL)
+   {
+      pgmoneta_log_error("Failed to allocate msg_request");
+      goto error;
+   }
+
+   memset(msg_request, 0, sizeof(struct message));
+
+   msg_request->data = full_request;
+   msg_request->length = strlen(full_request) + 1;
+
+   error = 0;
+req:
+   if (error < 5)
+   {
+      status = pgmoneta_write_message(http->ssl, http->socket, msg_request);
+      if (status != MESSAGE_STATUS_OK)
+      {
+         error++;
+         pgmoneta_log_debug("Write failed, retrying (%d/5)", error);
+         goto req;
+      }
+   }
+   else
+   {
+      pgmoneta_log_error("Failed to write after 5 attempts");
+      goto error;
+   }
+
+   status = pgmoneta_http_read(http->ssl, http->socket, &response);
+
+   if (response == NULL)
+   {
+      pgmoneta_log_error("No response data collected");
+      goto error;
+   }
 
-   if (res != CURLE_OK)
+   if (http_extract_headers_body(response, http))
    {
+      pgmoneta_log_error("Failed to extract headers and body");
       goto error;
    }
 
+   pgmoneta_log_debug("HTTP Headers: %s", http->headers ? http->headers : "NULL");
+   pgmoneta_log_debug("HTTP Body: %s", http->body ? http->body : "NULL");
+
+   free(request);
+   free(full_request);
+   free(response);
+   free(msg_request);
+   free(user_agent);
+
+   free(http->request_headers);
+   http->request_headers = NULL;
+
    return 0;
 
 error:
+   free(request);
+   free(full_request);
+   free(response);
+   free(msg_request);
+   free(user_agent);
+   free(http->request_headers);
+   http->request_headers = NULL;
+   return 1;
+}
+
+int
+pgmoneta_http_connect(char* hostname, int port, bool secure, struct http** result)
+{
+   struct http* h = NULL;
+   int socket_fd = -1;
+   SSL* ssl = NULL;
+   SSL_CTX* ctx = NULL;
+
+   pgmoneta_log_debug("Connecting to %s:%d (secure: %d)", hostname, port, secure);
+   h = (struct http*) malloc(sizeof(struct http));
+   if (h == NULL)
+   {
+      pgmoneta_log_error("Failed to allocate HTTP structure");
+      goto error;
+   }
+
+   memset(h, 0, sizeof(struct http));
+
+   if (pgmoneta_connect(hostname, port, &socket_fd))
+   {
+      pgmoneta_log_error("Failed to connect to %s:%d", hostname, port);
+      goto error;
+   }
+
+   h->socket = socket_fd;
+
+   if (secure)
+   {
+      if (pgmoneta_create_ssl_ctx(true, &ctx))
+      {
+         pgmoneta_log_error("Failed to create SSL context");
+         goto error;
+      }
+
+      if (SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION) == 0)
+      {
+         pgmoneta_log_error("Failed to set minimum TLS version");
+         goto error;
+      }
+
+      ssl = SSL_new(ctx);
+      if (ssl == NULL)
+      {
+         pgmoneta_log_error("Failed to create SSL structure");
+         goto error;
+      }
+
+      if (SSL_set_fd(ssl, socket_fd) == 0)
+      {
+         pgmoneta_log_error("Failed to set SSL file descriptor");
+         goto error;
+      }
+
+      int connect_result;
+      do
+      {
+         connect_result = SSL_connect(ssl);
+
+         if (connect_result != 1)
+         {
+            int err = SSL_get_error(ssl, connect_result);
+            switch (err)
+            {
+               case SSL_ERROR_WANT_READ:
+               case SSL_ERROR_WANT_WRITE:
+                  continue;
+               default:
+                  pgmoneta_log_error("SSL connection failed: %s", ERR_error_string(err, NULL));
+                  goto error;
+            }
+         }
+      }
+      while (connect_result != 1);
+
+      h->ssl = ssl;
+   }
 
+   *result = h;
+
+   return 0;
+
+error:
+   if (ssl != NULL)
+   {
+      SSL_free(ssl);
+   }
+   if (ctx != NULL)
+   {
+      SSL_CTX_free(ctx);
+   }
+   if (socket_fd != -1)
+   {
+      pgmoneta_disconnect(socket_fd);
+   }
+   free(h);
    return 1;
 }
 
 int
-pgmoneta_http_set_request_option(CURL* handle, bool request_type)
+pgmoneta_http_post(struct http* http, char* hostname, char* path, char* data, size_t length)
 {
-   CURLcode res = -1;
+   struct message* msg_request = NULL;
+   int error = 0;
+   int status;
+   char* request = NULL;
+   char* full_request = NULL;
+   char* response = NULL;
+   char* user_agent = NULL;
+   char content_length[32];
+   
+   pgmoneta_log_trace("Starting pgmoneta_http_post");
+   if (http_build_header(PGMONETA_HTTP_POST, path, &request))
+   {
+      pgmoneta_log_error("Failed to build HTTP header");
+      goto error;
+   }
+
+   pgmoneta_http_add_header(http, "Host", hostname);
+   user_agent = pgmoneta_append(user_agent, "pgmoneta/");
+   user_agent = pgmoneta_append(user_agent, VERSION);
+   pgmoneta_http_add_header(http, "User-Agent", user_agent);
+   pgmoneta_http_add_header(http, "Connection", "close");
 
-   if (handle == NULL)
+   sprintf(content_length, "%zu", length);
+   pgmoneta_http_add_header(http, "Content-Length", content_length);
+   pgmoneta_http_add_header(http, "Content-Type", "application/x-www-form-urlencoded");
+
+   full_request = pgmoneta_append(NULL, request);
+   full_request = pgmoneta_append(full_request, http->request_headers);
+   full_request = pgmoneta_append(full_request, "\r\n");
+
+   if (data && length > 0)
+   {
+      full_request = pgmoneta_append(full_request, data);
+   }
+
+   msg_request = (struct message*)malloc(sizeof(struct message));
+   if (msg_request == NULL)
    {
+      pgmoneta_log_error("Failed to allocate msg_request");
       goto error;
    }
 
-   if (request_type == HTTP_GET)
+   memset(msg_request, 0, sizeof(struct message));
+
+   msg_request->data = full_request;
+   msg_request->length = strlen(full_request) + 1;
+
+   error = 0;
+req:
+   if (error < 5)
    {
-      res = curl_easy_setopt(handle, CURLOPT_HTTPGET, 1L);
+      status = pgmoneta_write_message(http->ssl, http->socket, msg_request);
+      if (status != MESSAGE_STATUS_OK)
+      {
+         error++;
+         pgmoneta_log_debug("Write failed, retrying (%d/5)", error);
+         goto req;
+      }
    }
-   else if (request_type == HTTP_PUT)
+   else
    {
-      res = curl_easy_setopt(handle, CURLOPT_UPLOAD, 1L);
+      pgmoneta_log_error("Failed to write after 5 attempts");
+      goto error;
    }
 
-   if (res != CURLE_OK)
+   status = pgmoneta_http_read(http->ssl, http->socket, &response);
+
+   if (response == NULL)
    {
+      pgmoneta_log_error("No response data collected");
       goto error;
    }
 
+   if (http_extract_headers_body(response, http))
+   {
+      pgmoneta_log_error("Failed to extract headers and body");
+      goto error;
+   }
+
+   free(request);
+   free(full_request);
+   free(response);
+   free(msg_request);
+   free(user_agent);
+
+   free(http->request_headers);
+   http->request_headers = NULL;
+
    return 0;
 
 error:
-
+   free(request);
+   free(full_request);
+   free(response);
+   free(msg_request);
+   free(user_agent);
+   free(http->request_headers);
+   http->request_headers = NULL;
    return 1;
 }
 
 int
-pgmoneta_http_set_url_option(CURL* handle, char* url)
+pgmoneta_http_put(struct http* http, char* hostname, char* path, const void* data, size_t length)
 {
-   CURLcode res;
+   struct message* msg_request = NULL;
+   int error = 0;
+   int status;
+   char* request = NULL;
+   char* full_request = NULL;
+   char* response = NULL;
+   char* user_agent = NULL;
+   char* complete_request = NULL;
+   char content_length[32];
+
+   pgmoneta_log_trace("Starting pgmoneta_http_put");
+   if (http_build_header(PGMONETA_HTTP_PUT, path, &request))
+   {
+      pgmoneta_log_error("Failed to build HTTP header");
+      goto error;
+   }
+
+   pgmoneta_http_add_header(http, "Host", hostname);
+   user_agent = pgmoneta_append(user_agent, "pgmoneta/");
+   user_agent = pgmoneta_append(user_agent, VERSION);
+   pgmoneta_http_add_header(http, "User-Agent", user_agent);
+   pgmoneta_http_add_header(http, "Connection", "close");
+
+   sprintf(content_length, "%zu", length);
+   pgmoneta_http_add_header(http, "Content-Length", content_length);
+   pgmoneta_http_add_header(http, "Content-Type", "application/octet-stream");
+
+   full_request = pgmoneta_append(NULL, request);
+   full_request = pgmoneta_append(full_request, http->request_headers);
+   full_request = pgmoneta_append(full_request, "\r\n");
+
+   size_t headers_len = strlen(full_request);
+   size_t total_len = headers_len + length;
+
+   complete_request = malloc(total_len + 1);
+   if (complete_request == NULL)
+   {
+      pgmoneta_log_error("Failed to allocate complete request");
+      goto error;
+   }
+
+   memcpy(complete_request, full_request, headers_len);
+
+   if (data && length > 0)
+   {
+      memcpy(complete_request + headers_len, data, length);
+   }
 
-   if (handle == NULL)
+   complete_request[total_len] = '\0';
+
+   msg_request = (struct message*)malloc(sizeof(struct message));
+   if (msg_request == NULL)
    {
+      pgmoneta_log_error("Failed to allocate msg_request");
       goto error;
    }
 
-   res = curl_easy_setopt(handle, CURLOPT_URL, url);
+   memset(msg_request, 0, sizeof(struct message));
+
+   msg_request->data = complete_request;
+   msg_request->length = total_len + 1;
+
+   error = 0;
+req:
+   if (error < 5)
+   {
+      status = pgmoneta_write_message(http->ssl, http->socket, msg_request);
+      if (status != MESSAGE_STATUS_OK)
+      {
+         error++;
+         pgmoneta_log_debug("Write failed, retrying (%d/5)", error);
+         goto req;
+      }
+   }
+   else
+   {
+      pgmoneta_log_error("Failed to write after 5 attempts");
+      goto error;
+   }
+
+   status = pgmoneta_http_read(http->ssl, http->socket, &response);
+
+   if (response == NULL)
+   {
+      pgmoneta_log_error("No response data collected");
+      goto error;
+   }
 
-   if (res != CURLE_OK)
+   if (http_extract_headers_body(response, http))
    {
+      pgmoneta_log_error("Failed to extract headers and body");
       goto error;
    }
 
+   free(request);
+   free(full_request);
+   free(response);
+   free(msg_request->data);
+   free(msg_request);
+   free(user_agent);
+
+   free(http->request_headers);
+   http->request_headers = NULL;
+
    return 0;
 
 error:
+   free(request);
+   free(full_request);
+   free(response);
+   free(complete_request);
+   free(msg_request);
+   free(user_agent);
+   free(http->request_headers);
+   http->request_headers = NULL;
 
    return 1;
 }
+
+int
+pgmoneta_http_put_file(struct http* http, char* hostname, char* path, FILE* file, size_t file_size, char* content_type)
+{
+   struct message* msg_request = NULL;
+   int error = 0;
+   int status;
+   char* request = NULL;
+   char* header_part = NULL;
+   char* response = NULL;
+   char* user_agent = NULL;
+   char* full_request = NULL;
+   char content_length[32];
+   void* file_buffer = NULL;
+
+   pgmoneta_log_trace("Starting pgmoneta_http_put_file");
+   if (file == NULL)
+   {
+      pgmoneta_log_error("File is NULL");
+      goto error;
+   }
+
+   if (http_build_header(PGMONETA_HTTP_PUT, path, &request))
+   {
+      pgmoneta_log_error("Failed to build HTTP header");
+      goto error;
+   }
+
+   pgmoneta_http_add_header(http, "Host", hostname);
+   user_agent = pgmoneta_append(user_agent, "pgmoneta/");
+   user_agent = pgmoneta_append(user_agent, VERSION);
+   pgmoneta_http_add_header(http, "User-Agent", user_agent);
+   pgmoneta_http_add_header(http, "Connection", "close");
+
+   sprintf(content_length, "%zu", file_size);
+   pgmoneta_http_add_header(http, "Content-Length", content_length);
+
+   /* default to application/octet-stream if not specified */
+   char* type = content_type ? content_type : "application/octet-stream";
+   pgmoneta_http_add_header(http, "Content-Type", type);
+
+   header_part = pgmoneta_append(NULL, request);
+   header_part = pgmoneta_append(header_part, http->request_headers);
+   header_part = pgmoneta_append(header_part, "\r\n");
+
+   pgmoneta_log_trace("File size: %zu", file_size);
+
+   rewind(file);
+
+   file_buffer = malloc(file_size);
+   if (file_buffer == NULL)
+   {
+      pgmoneta_log_error("Failed to allocate memory for file content: %zu bytes", file_size);
+      goto error;
+   }
+
+   size_t bytes_read = fread(file_buffer, 1, file_size, file);
+   if (bytes_read != file_size)
+   {
+      pgmoneta_log_error("Failed to read entire file. Expected %zu bytes, got %zu", file_size, bytes_read);
+      goto error;
+   }
+
+   pgmoneta_log_trace("Read %zu bytes from file", bytes_read);
+
+   msg_request = (struct message*)malloc(sizeof(struct message));
+   if (msg_request == NULL)
+   {
+      pgmoneta_log_error("Failed to allocate msg_request");
+      goto error;
+   }
+
+   memset(msg_request, 0, sizeof(struct message));
+
+   size_t header_len = strlen(header_part);
+   size_t total_len = header_len + file_size;
+
+   full_request = malloc(total_len + 1);
+   if (full_request == NULL)
+   {
+      pgmoneta_log_error("Failed to allocate memory for full request: %zu bytes", total_len + 1);
+      goto error;
+   }
+
+   memcpy(full_request, header_part, header_len);
+
+   memcpy(full_request + header_len, file_buffer, file_size);
+
+   full_request[total_len] = '\0';
+
+   pgmoneta_log_trace("Setting msg_request data, total size: %zu", total_len);
+   msg_request->data = full_request;
+   msg_request->length = total_len;
+
+   error = 0;
+req:
+   if (error < 5)
+   {
+      status = pgmoneta_write_message(http->ssl, http->socket, msg_request);
+      if (status != MESSAGE_STATUS_OK)
+      {
+         error++;
+         pgmoneta_log_debug("Write failed, retrying (%d/5)", error);
+         goto req;
+      }
+   }
+   else
+   {
+      pgmoneta_log_error("Failed to write after 5 attempts");
+      goto error;
+   }
+
+   status = pgmoneta_http_read(http->ssl, http->socket, &response);
+
+   if (response == NULL)
+   {
+      pgmoneta_log_error("No response data collected");
+      goto error;
+   }
+
+   if (http_extract_headers_body(response, http))
+   {
+      pgmoneta_log_error("Failed to extract headers and body");
+      goto error;
+   }
+
+   int status_code = 0;
+   if (http->headers && sscanf(http->headers, "HTTP/1.1 %d", &status_code) == 1)
+   {
+      pgmoneta_log_debug("HTTP status code: %d", status_code);
+      if (status_code >= 200 && status_code < 300)
+      {
+         pgmoneta_log_debug("HTTP request successful");
+      }
+      else
+      {
+         pgmoneta_log_error("HTTP request failed with status code: %d", status_code);
+      }
+   }
+
+   free(request);
+   free(header_part);
+   free(response);
+   free(file_buffer);
+   free(full_request);
+   free(msg_request);
+   free(user_agent);
+
+   free(http->request_headers);
+   http->request_headers = NULL;
+
+   return (status_code >= 200 && status_code < 300) ? 0 : 1;
+
+error:
+   free(request);
+   free(header_part);
+   free(response);
+   free(file_buffer);
+   free(full_request);
+   free(msg_request);
+   free(user_agent);
+   free(http->request_headers);
+   http->request_headers = NULL;
+
+   return 1;
+}
+
+int
+pgmoneta_http_read(SSL* ssl, int socket, char** response_text)
+{
+   char buffer[8192];
+   ssize_t bytes_read;
+   int total_bytes = 0;
+
+   *response_text = NULL;
+
+   while (1)
+   {
+      if (ssl)
+      {
+         bytes_read = SSL_read(ssl, buffer, sizeof(buffer) - 1);
+         if (bytes_read <= 0)
+         {
+            int err = SSL_get_error(ssl, bytes_read);
+            if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE)
+            {
+               continue;
+            }
+            break;
+         }
+      }
+      else
+      {
+         bytes_read = read(socket, buffer, sizeof(buffer) - 1);
+         if (bytes_read < 0)
+         {
+            if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)
+            {
+               continue;
+            }
+            break;
+         }
+         else if (bytes_read == 0)
+         {
+            break;
+         }
+      }
+
+      buffer[bytes_read] = '\0';
+      *response_text = pgmoneta_append(*response_text, buffer);
+      total_bytes += bytes_read;
+
+      if (strstr(buffer, "\r\n0\r\n\r\n") || bytes_read < sizeof(buffer) - 1)
+      {
+         break;
+      }
+   }
+
+   return total_bytes > 0 ? MESSAGE_STATUS_OK : MESSAGE_STATUS_ERROR;
+}
+
+int
+pgmoneta_http_disconnect(struct http* http)
+{
+   if (http != NULL)
+   {
+      if (http->ssl != NULL)
+      {
+         pgmoneta_close_ssl(http->ssl);
+         http->ssl = NULL;
+      }
+
+      if (http->socket != -1)
+      {
+         if (pgmoneta_disconnect(http->socket))
+         {
+            return 1;
+         }
+         http->socket = -1;
+      }
+
+      if (http->headers != NULL)
+      {
+         free(http->headers);
+         http->headers = NULL;
+      }
+
+      if (http->body != NULL)
+      {
+         free(http->body);
+         http->body = NULL;
+      }
+
+      if (http->request_headers != NULL)
+      {
+         free(http->request_headers);
+         http->request_headers = NULL;
+      }
+   }
+   return 0;
+}
\ No newline at end of file
diff --git a/src/libpgmoneta/message.c b/src/libpgmoneta/message.c
index d4e7cd83b..ce32f91de 100644
--- a/src/libpgmoneta/message.c
+++ b/src/libpgmoneta/message.c
@@ -67,7 +67,7 @@ pgmoneta_read_block_message(SSL* ssl, int socket, struct message** msg)
 {
    if (ssl == NULL)
    {
-      return read_message(socket, true, 0, msg);
+      return read_message(socket, true, 5, msg);
    }
 
    return ssl_read_message(ssl, 0, msg);
diff --git a/src/libpgmoneta/se_azure.c b/src/libpgmoneta/se_azure.c
index 24fc17269..b3764d988 100644
--- a/src/libpgmoneta/se_azure.c
+++ b/src/libpgmoneta/se_azure.c
@@ -51,8 +51,6 @@ static int azure_send_upload_request(char* local_root, char* azure_root, char* r
 static char* azure_get_host(void);
 static char* azure_get_basepath(int server, char* identifier);
 
-static CURL* curl = NULL;
-
 struct workflow*
 pgmoneta_storage_create_azure(void)
 {
@@ -84,12 +82,6 @@ azure_storage_setup(char* name __attribute__((unused)), struct art* nodes)
 
    config = (struct main_configuration*)shmem;
 
-   curl = curl_easy_init();
-   if (curl == NULL)
-   {
-      goto error;
-   }
-
 #ifdef DEBUG
    if (pgmoneta_log_is_enabled(PGMONETA_LOGGING_LEVEL_DEBUG1))
    {
@@ -108,9 +100,6 @@ azure_storage_setup(char* name __attribute__((unused)), struct art* nodes)
    pgmoneta_log_debug("Azure storage engine (setup): %s/%s", config->common.servers[server].name, label);
 
    return 0;
-
-error:
-   return 1;
 }
 
 static int
@@ -212,8 +201,6 @@ azure_storage_teardown(char* name __attribute__((unused)), struct art* nodes)
 
    pgmoneta_delete_directory(root);
 
-   curl_easy_cleanup(curl);
-
    pgmoneta_log_debug("Azure storage engine (teardown): %s/%s", config->common.servers[server].name, label);
 
    free(root);
@@ -323,26 +310,32 @@ azure_send_upload_request(char* local_root, char* azure_root, char* relative_pat
    char utc_date[UTC_TIME_LENGTH];
    char* string_to_sign = NULL;
    char* signing_key = NULL;
-   char* signature = NULL;
    char* base64_signature = NULL;
    size_t base64_signature_length;
    char* local_path = NULL;
    char* azure_path = NULL;
+   char azure_put_path[MAX_PATH];
    char* azure_host = NULL;
-   char* azure_url = NULL;
    char* auth_value = NULL;
    unsigned char* signature_hmac = NULL;
-   unsigned char* signature_hex = NULL;
    int hmac_length = 0;
    size_t signing_key_length = 0;
    FILE* file = NULL;
    struct stat file_info;
-   CURLcode res = -1;
-   struct curl_slist* chunk = NULL;
+   int res = -1;
+   struct http* http = NULL;
    struct main_configuration* config;
+   char size_str[64];
 
    config = (struct main_configuration*)shmem;
 
+   // Check for storage account name contains spaces, which will cause issues
+   if (strchr(config->azure_storage_account, ' ') != NULL)
+   {
+      pgmoneta_log_error("Azure storage account name contains spaces: '%s'. This is not allowed.", config->azure_storage_account);
+      goto error;
+   }
+
    local_path = pgmoneta_append(local_path, local_root);
    local_path = pgmoneta_append(local_path, relative_path);
 
@@ -367,16 +360,17 @@ azure_send_upload_request(char* local_root, char* azure_root, char* relative_pat
       goto error;
    }
 
-   // Construct string to sign.
+   // Since we specifiy octet-stream in header, we include it to our string_to_sign
    if (file_info.st_size == 0)
    {
-      string_to_sign = pgmoneta_append(string_to_sign, "PUT\n\n\n\n\n\n\n\n\n\n\n\nx-ms-blob-type:BlockBlob\nx-ms-date:");
+      string_to_sign = pgmoneta_append(string_to_sign, "PUT\n\n\n\n\napplication/octet-stream\n\n\n\n\n\n\nx-ms-blob-type:BlockBlob\nx-ms-date:");
    }
    else
    {
       string_to_sign = pgmoneta_append(string_to_sign, "PUT\n\n\n");
-      string_to_sign = pgmoneta_append(string_to_sign, (char*)file_info.st_size);
-      string_to_sign = pgmoneta_append(string_to_sign, "\n\n\n\n\n\n\n\n\nx-ms-blob-type:BlockBlob\nx-ms-date:");
+      snprintf(size_str, sizeof(size_str), "%ld", (long)file_info.st_size);
+      string_to_sign = pgmoneta_append(string_to_sign, size_str);
+      string_to_sign = pgmoneta_append(string_to_sign, "\n\napplication/octet-stream\n\n\n\n\n\n\nx-ms-blob-type:BlockBlob\nx-ms-date:");
    }
 
    string_to_sign = pgmoneta_append(string_to_sign, utc_date);
@@ -405,53 +399,83 @@ azure_send_upload_request(char* local_root, char* azure_root, char* relative_pat
    auth_value = pgmoneta_append(auth_value, ":");
    auth_value = pgmoneta_append(auth_value, base64_signature);
 
-   chunk = pgmoneta_http_add_header(chunk, "Authorization", auth_value);
-
-   chunk = pgmoneta_http_add_header(chunk, "x-ms-blob-type", "BlockBlob");
-
-   chunk = pgmoneta_http_add_header(chunk, "x-ms-date", utc_date);
-
-   chunk = pgmoneta_http_add_header(chunk, "x-ms-version", "2021-08-06");
+   azure_host = azure_get_host();
 
-   if (pgmoneta_http_set_header_option(curl, chunk))
+   // Create HTTP connection
+   if (pgmoneta_http_connect(azure_host, 443, true, &http))
    {
+      pgmoneta_log_error("Failed to connect to Azure host: %s", azure_host);
       goto error;
    }
 
-   azure_host = azure_get_host();
-
-   azure_url = pgmoneta_append(azure_url, "https://");
-   azure_url = pgmoneta_append(azure_url, azure_host);
-   azure_url = pgmoneta_append(azure_url, "/");
-   azure_url = pgmoneta_append(azure_url, azure_path);
-
-   pgmoneta_http_set_request_option(curl, HTTP_PUT);
-
-   pgmoneta_http_set_url_option(curl, azure_url);
+   // Add headers
+   pgmoneta_http_add_header(http, "Authorization", auth_value);
+   pgmoneta_http_add_header(http, "x-ms-blob-type", "BlockBlob");
+   pgmoneta_http_add_header(http, "x-ms-date", utc_date);
+   pgmoneta_http_add_header(http, "x-ms-version", "2021-08-06");
 
-   curl_easy_setopt(curl, CURLOPT_READDATA, (void*) file);
+   // Construct PUT path
+   snprintf(azure_put_path, sizeof(azure_put_path), "/%s/%s", config->azure_container, azure_path);
 
-   curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)file_info.st_size);
-
-   res = curl_easy_perform(curl);
-   if (res != CURLE_OK)
+   // Send PUT request with file
+   res = pgmoneta_http_put_file(http, azure_host, azure_put_path, file, file_info.st_size, "application/octet-stream");
+   if (res != 0)
    {
-      fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
+      pgmoneta_log_error("Azure upload failed for %s", local_path);
+      pgmoneta_log_error("Failed to execute HTTP PUT request");
       goto error;
    }
+   else
+   {
+      int status_code = 0;
+      if (http->headers && sscanf(http->headers, "HTTP/1.1 %d", &status_code) == 1)
+      {
+         pgmoneta_log_info("Azure HTTP status code: %d", status_code);
+         if (status_code >= 200 && status_code < 300)
+         {
+            pgmoneta_log_info("Successfully uploaded file to Azure: %s", azure_path);
+
+            char* azure_url = NULL;
+            azure_url = pgmoneta_append(azure_url, "https://");
+            azure_url = pgmoneta_append(azure_url, azure_host);
+            azure_url = pgmoneta_append(azure_url, "/");
+            azure_url = pgmoneta_append(azure_url, config->azure_container);
+            azure_url = pgmoneta_append(azure_url, "/");
+            azure_url = pgmoneta_append(azure_url, azure_path);
+
+            pgmoneta_log_info("Blob URL: %s", azure_url);
+            free(azure_url);
+         }
+         else
+         {
+            pgmoneta_log_error("Azure upload failed with status code: %d. Failed to upload: %s to Azure path: %s. Azure container: %s, host: %s. Response headers: %s. Response body: %s",
+                               status_code, local_path, azure_path,
+                               config->azure_container, azure_host,
+                               http->headers ? http->headers : "None",
+                               http->body ? http->body : "None");
+            goto error;
+         }
+      }
+      else
+      {
+         pgmoneta_log_error("Azure upload failed - could not parse HTTP status code. Failed to upload: %s to Azure path: %s. Response headers: %s",
+                            local_path, azure_path,
+                            http->headers ? http->headers : "None");
+         goto error;
+      }
+   }
 
    free(local_path);
    free(azure_path);
-   free(azure_url);
    free(azure_host);
-   free(signature);
    free(base64_signature);
    free(signature_hmac);
-   free(signature_hex);
    free(string_to_sign);
    free(auth_value);
+   free(signing_key);
 
-   curl_slist_free_all(chunk);
+   pgmoneta_http_disconnect(http);
+   free(http);
 
    fclose(file);
    return 0;
@@ -468,19 +492,14 @@ azure_send_upload_request(char* local_root, char* azure_root, char* relative_pat
       free(azure_path);
    }
 
-   if (azure_url != NULL)
-   {
-      free(azure_url);
-   }
-
    if (azure_host != NULL)
    {
       free(azure_host);
    }
 
-   if (signature != NULL)
+   if (signing_key != NULL)
    {
-      free(signature);
+      free(signing_key);
    }
 
    if (base64_signature != NULL)
@@ -493,11 +512,6 @@ azure_send_upload_request(char* local_root, char* azure_root, char* relative_pat
       free(signature_hmac);
    }
 
-   if (signature_hex != NULL)
-   {
-      free(signature_hex);
-   }
-
    if (string_to_sign != NULL)
    {
       free(string_to_sign);
@@ -508,9 +522,10 @@ azure_send_upload_request(char* local_root, char* azure_root, char* relative_pat
       free(auth_value);
    }
 
-   if (chunk != NULL)
+   if (http != NULL)
    {
-      curl_slist_free_all(chunk);
+      pgmoneta_http_disconnect(http);
+      free(http);
    }
 
    if (file != NULL)
@@ -530,8 +545,7 @@ azure_get_host()
    config = (struct main_configuration*)shmem;
 
    host = pgmoneta_append(host, config->azure_storage_account);
-   host = pgmoneta_append(host, ".blob.core.windows.net/");
-   host = pgmoneta_append(host, config->azure_container);
+   host = pgmoneta_append(host, ".blob.core.windows.net");
 
    return host;
 }
diff --git a/src/libpgmoneta/se_s3.c b/src/libpgmoneta/se_s3.c
index 51e54e41f..abf825f50 100644
--- a/src/libpgmoneta/se_s3.c
+++ b/src/libpgmoneta/se_s3.c
@@ -52,8 +52,6 @@ static int s3_send_upload_request(char* local_root, char* s3_root, char* relativ
 static char* s3_get_host(void);
 static char* s3_get_basepath(int server, char* identifier);
 
-static CURL* curl = NULL;
-
 struct workflow*
 pgmoneta_storage_create_s3(void)
 {
@@ -103,16 +101,7 @@ s3_storage_setup(char* name __attribute__((unused)), struct art* nodes)
 
    pgmoneta_log_debug("S3 storage engine (setup): %s/%s", config->common.servers[server].name, label);
 
-   curl = curl_easy_init();
-   if (curl == NULL)
-   {
-      goto error;
-   }
-
    return 0;
-
-error:
-   return 1;
 }
 
 static int
@@ -216,8 +205,6 @@ s3_storage_teardown(char* name __attribute__((unused)), struct art* nodes)
 
    pgmoneta_delete_directory(root);
 
-   curl_easy_cleanup(curl);
-
    free(root);
 
    return 0;
@@ -297,11 +284,12 @@ s3_send_upload_request(char* local_root, char* s3_root, char* relative_path)
    char* string_to_sign = NULL;
    char* s3_host = NULL;
    char* s3_url = NULL;
+   char* s3_path = NULL;
+   char s3_put_path[MAX_PATH];
    char* file_sha256 = NULL;
    char* canonical_request_sha256 = NULL;
    char* key = NULL;
    char* local_path = NULL;
-   char* s3_path = NULL;
    unsigned char* date_key_hmac = NULL;
    unsigned char* date_region_key_hmac = NULL;
    unsigned char* date_region_service_key_hmac = NULL;
@@ -311,8 +299,8 @@ s3_send_upload_request(char* local_root, char* s3_root, char* relative_path)
    int hmac_length = 0;
    FILE* file = NULL;
    struct stat file_info;
-   CURLcode res = -1;
-   struct curl_slist* chunk = NULL;
+   int res = -1;
+   struct http* http = NULL;
    struct main_configuration* config;
 
    config = (struct main_configuration*)shmem;
@@ -398,25 +386,20 @@ s3_send_upload_request(char* local_root, char* s3_root, char* relative_path)
    auth_value = pgmoneta_append(auth_value, "/s3/aws4_request,SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-amz-storage-class,Signature=");
    auth_value = pgmoneta_append(auth_value, (char*)signature_hex);
 
-   chunk = pgmoneta_http_add_header(chunk, "Authorization", auth_value);
-
-   chunk = pgmoneta_http_add_header(chunk, "Host", s3_host);
-
-   chunk = pgmoneta_http_add_header(chunk, "x-amz-content-sha256", file_sha256);
-
-   chunk = pgmoneta_http_add_header(chunk, "x-amz-date", long_date);
-
-   chunk = pgmoneta_http_add_header(chunk, "x-amz-storage-class", "REDUCED_REDUNDANCY");
-
-   if (pgmoneta_http_set_header_option(curl, chunk))
+   // Connect to S3 with our custom HTTP implementation
+   if (pgmoneta_http_connect(s3_host, 443, true, &http))
    {
       goto error;
    }
 
-   s3_url = pgmoneta_append(s3_url, "https://");
-   s3_url = pgmoneta_append(s3_url, s3_host);
-   s3_url = pgmoneta_append(s3_url, "/");
-   s3_url = pgmoneta_append(s3_url, s3_path);
+   // Add headers
+   pgmoneta_http_add_header(http, "Authorization", auth_value);
+   pgmoneta_http_add_header(http, "x-amz-content-sha256", file_sha256);
+   pgmoneta_http_add_header(http, "x-amz-date", long_date);
+   pgmoneta_http_add_header(http, "x-amz-storage-class", "REDUCED_REDUNDANCY");
+
+   // Construct path for PUT request
+   snprintf(s3_put_path, sizeof(s3_put_path), "/%s", s3_path);
 
    file = fopen(local_path, "rb");
    if (file == NULL)
@@ -429,20 +412,37 @@ s3_send_upload_request(char* local_root, char* s3_root, char* relative_path)
       goto error;
    }
 
-   pgmoneta_http_set_request_option(curl, HTTP_PUT);
-
-   pgmoneta_http_set_url_option(curl, s3_url);
-
-   curl_easy_setopt(curl, CURLOPT_READDATA, (void*) file);
-
-   curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)file_info.st_size);
-
-   curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
-
-   res = curl_easy_perform(curl);
-   if (res != CURLE_OK)
+   // Perform the PUT request with file
+   res = pgmoneta_http_put_file(http, s3_host, s3_put_path, file, file_info.st_size, "application/octet-stream");
+   if (res == 0)
+   {
+      int status_code = 0;
+      if (http->headers && sscanf(http->headers, "HTTP/1.1 %d", &status_code) == 1)
+      {
+         pgmoneta_log_info("S3 HTTP status code: %d", status_code);
+         if (status_code >= 200 && status_code < 300)
+         {
+            pgmoneta_log_info("Successfully uploaded file to S3: %s", s3_path);
+            pgmoneta_log_info("Object URL: https://%s/%s", s3_host, s3_path);
+         }
+         else
+         {
+            pgmoneta_log_error("S3 upload failed with status code: %d. Failed to upload: %s to S3 path: %s. Response headers: %s. Response body: %s",
+                               status_code, s3_put_path, s3_path,
+                               http->headers, http->body ? http->body : "None");
+            goto error;
+         }
+      }
+      else
+      {
+         pgmoneta_log_error("Failed to parse HTTP status code from response. Response headers: %s",
+                            http->headers ? http->headers : "None");
+         goto error;
+      }
+   }
+   else
    {
-      fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
+      pgmoneta_log_error("pgmoneta_http_put_file() failed");
       goto error;
    }
 
@@ -463,7 +463,8 @@ s3_send_upload_request(char* local_root, char* s3_root, char* relative_path)
    free(string_to_sign);
    free(auth_value);
 
-   curl_slist_free_all(chunk);
+   pgmoneta_http_disconnect(http);
+   free(http);
 
    fclose(file);
    return 0;
@@ -550,9 +551,10 @@ s3_send_upload_request(char* local_root, char* s3_root, char* relative_path)
       free(auth_value);
    }
 
-   if (chunk != NULL)
+   if (http != NULL)
    {
-      curl_slist_free_all(chunk);
+      pgmoneta_http_disconnect(http);
+      free(http);
    }
 
    if (file != NULL)
diff --git a/src/libpgmoneta/utils.c b/src/libpgmoneta/utils.c
index b0858a547..9471bdb57 100644
--- a/src/libpgmoneta/utils.c
+++ b/src/libpgmoneta/utils.c
@@ -84,6 +84,7 @@ static int get_permissions(char* from, int* permissions);
 
 static void do_copy_file(struct worker_common* wc);
 static void do_delete_file(struct worker_common* wc);
+bool pgmoneta_is_number(char* str, int base);
 
 int32_t
 pgmoneta_get_request(struct message* msg)
@@ -4114,6 +4115,49 @@ pgmoneta_is_incremental_path(char* path)
    return pgmoneta_starts_with(name, INCREMENTAL_PREFIX);
 }
 
+bool
+pgmoneta_is_number(char* str, int base)
+{
+   if (str == NULL || strlen(str) == 0)
+   {
+      return false;
+   }
+
+   if (base != 10 && base != 16)
+   {
+      return false;
+   }
+
+   for (int i = 0; str[i] != '\0'; i++)
+   {
+      if (str[i] >= 48 && str[i] <= 57)
+      {
+         /* Standard numbers */
+      }
+      else if (str[i] == '\r' || str[i] == '\n')
+      {
+         /* Feeds */
+      }
+      else if (base == 16)
+      {
+         if ((str[i] >= 65 && str[i] <= 70) || (str[i] >= 97 && str[i] <= 102))
+         {
+            /* Hex */
+         }
+         else
+         {
+            return false;
+         }
+      }
+      else
+      {
+         return false;
+      }
+   }
+
+   return true;
+}
+
 int
 pgmoneta_split(const char* string, char*** results, int* count, char delimiter)
 {
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 542965ee7..ff2d12e01 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -29,8 +29,10 @@ if (check)
 
   set(SOURCES
     tsclient.c
+    tsclient_http.c
     testcases/pgmoneta_test_1.c
     testcases/pgmoneta_test_2.c
+    testcases/pgmoneta_test_3.c
     runner.c
   )
 
diff --git a/test/Dockerfile.testsuite b/test/Dockerfile.testsuite
index a5ce5f873..bfeef3f22 100644
--- a/test/Dockerfile.testsuite
+++ b/test/Dockerfile.testsuite
@@ -42,11 +42,11 @@ RUN dnf -y update && \
       git gcc clang cmake make \
       libev libev-devel openssl-devel systemd-devel \
       zlib zlib-devel libzstd-devel lz4-devel \
-      libssh-devel libcurl-devel libatomic \
+      libssh-devel libatomic \
       python3-docutils bzip2-devel libarchive-devel \
       net-tools check check-devel graphviz \
       doxygen pandoc texlive texlive-latex \
-      python3-pip curl ca-certificates wget \
+      python3-pip ca-certificates wget \
       libasan libubsan libstdc++-devel libgcc glibc-devel \
       gawk coreutils findutils iproute && \
     dnf clean all
diff --git a/test/include/tsclient.h b/test/include/tsclient.h
index 43c74e18d..5a419a6f1 100644
--- a/test/include/tsclient.h
+++ b/test/include/tsclient.h
@@ -95,6 +95,41 @@ pgmoneta_tsclient_execute_restore(char* server, char* backup_id, char* position)
 int
 pgmoneta_tsclient_execute_delete(char* server, char* backup_id);
 
+/**
+ * Execute HTTP test
+ * @return 0 upon success, otherwise 1
+ */
+int
+pgmoneta_tsclient_execute_http();
+
+/**
+ * Execute HTTPS test
+ * @return 0 upon success, otherwise 1
+ */
+int
+pgmoneta_tsclient_execute_https();
+
+/**
+ * Execute HTTP POST test
+ * @return 0 upon success, otherwise 1
+ */
+int
+pgmoneta_tsclient_execute_http_post();
+
+/**
+ * Execute HTTP PUT test
+ * @return 0 upon success, otherwise 1
+ */
+int
+pgmoneta_tsclient_execute_http_put();
+
+/**
+ * Execute HTTP PUT file test
+ * @return 0 upon success, otherwise 1
+ */
+int
+pgmoneta_tsclient_execute_http_put_file();
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/test/runner.c b/test/runner.c
index edf2075b0..be169f6d9 100644
--- a/test/runner.c
+++ b/test/runner.c
@@ -30,6 +30,7 @@
 
 #include "testcases/pgmoneta_test_1.h"
 #include "testcases/pgmoneta_test_2.h"
+#include "testcases/pgmoneta_test_3.h"
 
 int
 main(int argc, char* argv[])
@@ -44,6 +45,7 @@ main(int argc, char* argv[])
    int number_failed;
    Suite* s1;
    Suite* s2;
+   Suite* s3;
    SRunner* sr;
 
    if (pgmoneta_tsclient_init(argv[1]))
@@ -53,9 +55,11 @@ main(int argc, char* argv[])
 
    s1 = pgmoneta_test1_suite();
    s2 = pgmoneta_test2_suite();
+   s3 = pgmoneta_test3_suite();
 
    sr = srunner_create(s1);
    srunner_add_suite(sr, s2);
+   srunner_add_suite(sr, s3);
 
    // Run the tests in verbose mode
    srunner_run_all(sr, CK_VERBOSE);
diff --git a/test/testcases/pgmoneta_test_3.c b/test/testcases/pgmoneta_test_3.c
new file mode 100644
index 000000000..0a98ea185
--- /dev/null
+++ b/test/testcases/pgmoneta_test_3.c
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2025 The pgmoneta community
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list
+ * of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this
+ * list of conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include <tsclient.h>
+
+#include "pgmoneta_test_3.h"
+
+START_TEST(test_pgmoneta_http)
+{
+   int found = 0;
+   found = !pgmoneta_tsclient_execute_http();
+   ck_assert_msg(found, "success status not found");
+}
+END_TEST
+START_TEST(test_pgmoneta_https)
+{
+   int found = 0;
+   found = !pgmoneta_tsclient_execute_https();
+   ck_assert_msg(found, "success status not found");
+}
+END_TEST
+START_TEST(test_pgmoneta_http_post)
+{
+   int found = 0;
+   found = !pgmoneta_tsclient_execute_http_post();
+   ck_assert_msg(found, "success status not found");
+}
+END_TEST
+START_TEST(test_pgmoneta_http_put)
+{
+   int found = 0;
+   found = !pgmoneta_tsclient_execute_http_put();
+   ck_assert_msg(found, "success status not found");
+}
+END_TEST
+START_TEST(test_pgmoneta_http_put_file)
+{
+   int found = 0;
+   found = !pgmoneta_tsclient_execute_http_put_file();
+   ck_assert_msg(found, "success status not found");
+}
+END_TEST
+
+Suite*
+pgmoneta_test3_suite()
+{
+   Suite* s;
+   TCase* tc_core;
+   s = suite_create("pgmoneta_test3");
+
+   tc_core = tcase_create("Core");
+
+   tcase_set_timeout(tc_core, 60);
+   tcase_add_test(tc_core, test_pgmoneta_http);
+   tcase_add_test(tc_core, test_pgmoneta_https);
+   tcase_add_test(tc_core, test_pgmoneta_http_post);
+   tcase_add_test(tc_core, test_pgmoneta_http_put);
+   tcase_add_test(tc_core, test_pgmoneta_http_put_file);
+   suite_add_tcase(s, tc_core);
+
+   return s;
+}
\ No newline at end of file
diff --git a/test/testcases/pgmoneta_test_3.h b/test/testcases/pgmoneta_test_3.h
new file mode 100644
index 000000000..7691c4ce6
--- /dev/null
+++ b/test/testcases/pgmoneta_test_3.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2025 The pgmoneta community
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list
+ * of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this
+ * list of conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef PGMONETA_TEST3_H
+#define PGMONETA_TEST3_H
+
+#include <check.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/**
+ * Set up a suite of test cases for pgmoneta
+ * @return The result
+ */
+Suite*
+pgmoneta_test3_suite();
+
+#endif // PGMONETA_TEST3_H
\ No newline at end of file
diff --git a/test/tsclient_http.c b/test/tsclient_http.c
new file mode 100644
index 000000000..6e90b8c2d
--- /dev/null
+++ b/test/tsclient_http.c
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2025 The pgmoneta community
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list
+ * of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this
+ * list of conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include <pgmoneta.h>
+#include <http.h>
+#include <logging.h>
+#include <tsclient.h>
+
+int
+pgmoneta_tsclient_execute_http()
+{
+   int status;
+   struct http* h = NULL;
+
+   pgmoneta_init_logging();
+
+   const char* hostname = "postman-echo.com";
+   int port = 80;
+   bool secure = false;
+
+   if (pgmoneta_http_connect((char*)hostname, port, secure, &h))
+   {
+      return 1;
+   }
+
+   status = pgmoneta_http_get(h, (char*)hostname, "/get");
+   
+   pgmoneta_http_disconnect(h);
+   free(h);
+
+   return (status == 0) ? 0 : 1;
+}
+
+int
+pgmoneta_tsclient_execute_https()
+{
+   int status;
+   struct http* h = NULL;
+
+   pgmoneta_init_logging();
+
+   const char* hostname = "postman-echo.com";
+   int port = 443;
+   bool secure = true;
+
+   if (pgmoneta_http_connect((char*)hostname, port, secure, &h))
+   {
+      return 1;
+   }
+
+   status = pgmoneta_http_get(h, (char*)hostname, "/get");
+   
+   pgmoneta_http_disconnect(h);
+   free(h);
+
+   return (status == 0) ? 0 : 1;
+}
+
+int
+pgmoneta_tsclient_execute_http_post()
+{
+   int status;
+   struct http* h = NULL;
+
+   pgmoneta_init_logging();
+
+   const char* hostname = "postman-echo.com";
+   int port = 443;
+   bool secure = true;
+   const char* test_data = "name=pgmoneta&version=1.0";
+
+   if (pgmoneta_http_connect((char*)hostname, port, secure, &h))
+   {
+      return 1;
+   }
+
+   status = pgmoneta_http_post(h, (char*)hostname, "/post", (char*)test_data, strlen(test_data));
+   
+   pgmoneta_http_disconnect(h);
+   free(h);
+
+   return (status == 0) ? 0 : 1;
+}
+
+int
+pgmoneta_tsclient_execute_http_put()
+{
+   int status;
+   struct http* h = NULL;
+
+   pgmoneta_init_logging();
+
+   const char* hostname = "postman-echo.com";
+   int port = 443;
+   bool secure = true;
+   const char* test_data = "This is a test file content for PUT request";
+
+   if (pgmoneta_http_connect((char*)hostname, port, secure, &h))
+   {
+      return 1;
+   }
+
+   status = pgmoneta_http_put(h, (char*)hostname, "/put", (void*)test_data, strlen(test_data));
+   
+   pgmoneta_http_disconnect(h);
+   free(h);
+
+   return (status == 0) ? 0 : 1;
+}
+
+int
+pgmoneta_tsclient_execute_http_put_file()
+{
+   int status;
+   struct http* h = NULL;
+   FILE* temp_file = NULL;
+
+   pgmoneta_init_logging();
+
+   const char* hostname = "postman-echo.com";
+   int port = 443;
+   bool secure = true;
+   const char* test_data = "This is a test file content for PUT file request\nSecond line of test data\nThird line with some numbers: 12345";
+   size_t data_len = strlen(test_data);
+
+   temp_file = tmpfile();
+   if (temp_file == NULL)
+   {
+      return 1;
+   }
+
+   if (fwrite(test_data, 1, data_len, temp_file) != data_len)
+   {
+      fclose(temp_file);
+      return 1;
+   }
+
+   rewind(temp_file);
+
+   if (pgmoneta_http_connect((char*)hostname, port, secure, &h))
+   {
+      fclose(temp_file);
+      return 1;
+   }
+
+   status = pgmoneta_http_put_file(h, (char*)hostname, "/put", temp_file, data_len, "text/plain");
+   
+   pgmoneta_http_disconnect(h);
+   free(h);
+   fclose(temp_file);
+
+   return (status == 0) ? 0 : 1;
+}
\ No newline at end of file


Reviews:
user: login: jesperpedersen
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-05-06T14:11:36Z
user: login: jesperpedersen
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-05-10T16:04:49Z
user: login: jesperpedersen
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-05-11T03:52:04Z
user: login: jesperpedersen
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-05-11T15:28:23Z

Review comments:
diff_hunk: @@ -38,49 +38,110 @@ extern "C" {
 #include <stdbool.h>
 #include <stdint.h>
 #include <stdio.h>
-#include <curl/curl.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 
-#define HTTP_GET 0
-#define HTTP_PUT 1
+/* HTTP method definitions */
+#define PGMONETA_HTTP_GET 0
path: src/include/http.h
user: login: jesperpedersen
body: Constant alignment
created_at: 2025-05-06T14:04:59Z
author_association: MEMBER
diff_hunk: @@ -38,49 +38,110 @@ extern "C" {
 #include <stdbool.h>
 #include <stdint.h>
 #include <stdio.h>
-#include <curl/curl.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 
-#define HTTP_GET 0
-#define HTTP_PUT 1
+/* HTTP method definitions */
+#define PGMONETA_HTTP_GET 0
+#define PGMONETA_HTTP_POST 1
+#define PGMONETA_HTTP_PUT 2
+
+/** @struct http
+ * Defines a HTTP interaction
+ */
+struct http
+{
+   int endpoint;         /**< The endpoint */
path: src/include/http.h
user: login: jesperpedersen
body: Comment alignment
created_at: 2025-05-06T14:05:12Z
author_association: MEMBER
diff_hunk: @@ -38,49 +38,110 @@ extern "C" {
 #include <stdbool.h>
 #include <stdint.h>
 #include <stdio.h>
-#include <curl/curl.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 
-#define HTTP_GET 0
-#define HTTP_PUT 1
+/* HTTP method definitions */
+#define PGMONETA_HTTP_GET 0
+#define PGMONETA_HTTP_POST 1
+#define PGMONETA_HTTP_PUT 2
+
+/** @struct http
+ * Defines a HTTP interaction
+ */
+struct http
+{
+   int endpoint;         /**< The endpoint */
+   int socket;           /**< The socket descriptor */
+   char* body;           /**< The HTTP response body */
+   char* headers;        /**< The HTTP response headers */
+   char* request_headers; /**< The HTTP request headers */
+   SSL* ssl;             /**< The SSL connection (NULL for non-secure) */
+};
+
+/**
+ * Connect to an HTTP/HTTPS server
+ * @param hostname The host to connect to
+ * @param port The port number
+ * @param secure Use SSL if true
+ * @param result The resulting HTTP structure
+ * @return 0 upon success, otherwise 1
+ */
+int
+pgmoneta_http_connect(char* hostname, int port, bool secure, struct http** result);
+
+/**
+ * Disconnect and clean up HTTP resources
+ * @param http The HTTP structure
+ */
+void
path: src/include/http.h
user: login: jesperpedersen
body: int ?
created_at: 2025-05-06T14:05:26Z
author_association: MEMBER
diff_hunk: @@ -38,49 +38,110 @@ extern "C" {
 #include <stdbool.h>
 #include <stdint.h>
 #include <stdio.h>
-#include <curl/curl.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 
-#define HTTP_GET 0
-#define HTTP_PUT 1
+/* HTTP method definitions */
+#define PGMONETA_HTTP_GET 0
+#define PGMONETA_HTTP_POST 1
+#define PGMONETA_HTTP_PUT 2
+
+/** @struct http
+ * Defines a HTTP interaction
+ */
+struct http
+{
+   int endpoint;         /**< The endpoint */
+   int socket;           /**< The socket descriptor */
+   char* body;           /**< The HTTP response body */
+   char* headers;        /**< The HTTP response headers */
+   char* request_headers; /**< The HTTP request headers */
+   SSL* ssl;             /**< The SSL connection (NULL for non-secure) */
+};
+
+/**
+ * Connect to an HTTP/HTTPS server
+ * @param hostname The host to connect to
+ * @param port The port number
+ * @param secure Use SSL if true
+ * @param result The resulting HTTP structure
+ * @return 0 upon success, otherwise 1
+ */
+int
+pgmoneta_http_connect(char* hostname, int port, bool secure, struct http** result);
+
+/**
+ * Disconnect and clean up HTTP resources
+ * @param http The HTTP structure
+ */
+void
+pgmoneta_http_disconnect(struct http* http);
 
 /**
- * Add a header
- * @param chunk A linked list of strings
- * @param header The header
- * @param value The header's value
- * @return A new list pointer
+ * Add a header to the HTTP request
+ * @param http The HTTP structure
+ * @param name The header name
+ * @param value The header value
  */
-struct curl_slist*
-pgmoneta_http_add_header(struct curl_slist* chunk, char* header, char* value);
+void
path: src/include/http.h
user: login: jesperpedersen
body: This should int - in case the header can't be added
created_at: 2025-05-06T14:05:46Z
author_association: MEMBER
diff_hunk: @@ -38,49 +38,110 @@ extern "C" {
 #include <stdbool.h>
 #include <stdint.h>
 #include <stdio.h>
-#include <curl/curl.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 
-#define HTTP_GET 0
-#define HTTP_PUT 1
+/* HTTP method definitions */
+#define PGMONETA_HTTP_GET 0
+#define PGMONETA_HTTP_POST 1
+#define PGMONETA_HTTP_PUT 2
+
+/** @struct http
+ * Defines a HTTP interaction
+ */
+struct http
+{
+   int endpoint;         /**< The endpoint */
+   int socket;           /**< The socket descriptor */
+   char* body;           /**< The HTTP response body */
+   char* headers;        /**< The HTTP response headers */
+   char* request_headers; /**< The HTTP request headers */
+   SSL* ssl;             /**< The SSL connection (NULL for non-secure) */
+};
+
+/**
+ * Connect to an HTTP/HTTPS server
+ * @param hostname The host to connect to
+ * @param port The port number
+ * @param secure Use SSL if true
+ * @param result The resulting HTTP structure
+ * @return 0 upon success, otherwise 1
+ */
+int
+pgmoneta_http_connect(char* hostname, int port, bool secure, struct http** result);
+
+/**
+ * Disconnect and clean up HTTP resources
+ * @param http The HTTP structure
+ */
+void
+pgmoneta_http_disconnect(struct http* http);
 
 /**
- * Add a header
- * @param chunk A linked list of strings
- * @param header The header
- * @param value The header's value
- * @return A new list pointer
+ * Add a header to the HTTP request
+ * @param http The HTTP structure
+ * @param name The header name
+ * @param value The header value
  */
-struct curl_slist*
-pgmoneta_http_add_header(struct curl_slist* chunk, char* header, char* value);
+void
+pgmoneta_http_add_header(struct http* http, char* name, char* value);
+
+/**
+ * Read response data directly from a socket
+ * @param ssl SSL connection (can be NULL for non-secure connections)
+ * @param socket The socket to read from
+ * @param response_text Pointer to store the response text
+ * @return MESSAGE_STATUS_OK on success, MESSAGE_STATUS_ERROR otherwise
+ */
+int
+pgmoneta_http_direct_read(SSL* ssl, int socket, char** response_text);
path: src/include/http.h
user: login: jesperpedersen
body: Remove `direct`
created_at: 2025-05-06T14:06:13Z
author_association: MEMBER
diff_hunk: @@ -108,9 +100,6 @@ azure_storage_setup(char* name __attribute__((unused)), struct art* nodes)
    pgmoneta_log_debug("Azure storage engine (setup): %s/%s", config->common.servers[server].name, label);
 
    return 0;
-
path: src/libpgmoneta/se_azure.c
user: login: jesperpedersen
body: Is this always successful ?
created_at: 2025-05-06T14:07:25Z
author_association: MEMBER
diff_hunk: @@ -405,53 +398,74 @@ azure_send_upload_request(char* local_root, char* azure_root, char* relative_pat
    auth_value = pgmoneta_append(auth_value, ":");
    auth_value = pgmoneta_append(auth_value, base64_signature);
 
-   chunk = pgmoneta_http_add_header(chunk, "Authorization", auth_value);
-
-   chunk = pgmoneta_http_add_header(chunk, "x-ms-blob-type", "BlockBlob");
-
-   chunk = pgmoneta_http_add_header(chunk, "x-ms-date", utc_date);
-
-   chunk = pgmoneta_http_add_header(chunk, "x-ms-version", "2021-08-06");
+   azure_host = azure_get_host();
 
-   if (pgmoneta_http_set_header_option(curl, chunk))
+   // Create HTTP connection
+   if (pgmoneta_http_connect(azure_host, 443, true, &http))
    {
+      pgmoneta_log_error("Failed to connect to Azure host: %s", azure_host);
       goto error;
    }
 
-   azure_host = azure_get_host();
-
-   azure_url = pgmoneta_append(azure_url, "https://");
-   azure_url = pgmoneta_append(azure_url, azure_host);
-   azure_url = pgmoneta_append(azure_url, "/");
-   azure_url = pgmoneta_append(azure_url, azure_path);
+   // Add headers
+   pgmoneta_http_add_header(http, "Authorization", auth_value);
+   pgmoneta_http_add_header(http, "x-ms-blob-type", "BlockBlob");
+   pgmoneta_http_add_header(http, "x-ms-date", utc_date);
+   pgmoneta_http_add_header(http, "x-ms-version", "2021-08-06");
 
-   pgmoneta_http_set_request_option(curl, HTTP_PUT);
+   // Construct PUT path
+   char azure_put_path[512];
path: src/libpgmoneta/se_azure.c
user: login: jesperpedersen
body: Variables in the beginning of a function - or within a block
created_at: 2025-05-06T14:08:39Z
author_association: MEMBER
diff_hunk: @@ -405,53 +398,74 @@ azure_send_upload_request(char* local_root, char* azure_root, char* relative_pat
    auth_value = pgmoneta_append(auth_value, ":");
    auth_value = pgmoneta_append(auth_value, base64_signature);
 
-   chunk = pgmoneta_http_add_header(chunk, "Authorization", auth_value);
-
-   chunk = pgmoneta_http_add_header(chunk, "x-ms-blob-type", "BlockBlob");
-
-   chunk = pgmoneta_http_add_header(chunk, "x-ms-date", utc_date);
-
-   chunk = pgmoneta_http_add_header(chunk, "x-ms-version", "2021-08-06");
+   azure_host = azure_get_host();
 
-   if (pgmoneta_http_set_header_option(curl, chunk))
+   // Create HTTP connection
+   if (pgmoneta_http_connect(azure_host, 443, true, &http))
    {
+      pgmoneta_log_error("Failed to connect to Azure host: %s", azure_host);
       goto error;
    }
 
-   azure_host = azure_get_host();
-
-   azure_url = pgmoneta_append(azure_url, "https://");
-   azure_url = pgmoneta_append(azure_url, azure_host);
-   azure_url = pgmoneta_append(azure_url, "/");
-   azure_url = pgmoneta_append(azure_url, azure_path);
+   // Add headers
+   pgmoneta_http_add_header(http, "Authorization", auth_value);
+   pgmoneta_http_add_header(http, "x-ms-blob-type", "BlockBlob");
+   pgmoneta_http_add_header(http, "x-ms-date", utc_date);
+   pgmoneta_http_add_header(http, "x-ms-version", "2021-08-06");
 
-   pgmoneta_http_set_request_option(curl, HTTP_PUT);
+   // Construct PUT path
+   char azure_put_path[512];
+   snprintf(azure_put_path, sizeof(azure_put_path), "/%s/%s", config->azure_container, azure_path);
 
-   pgmoneta_http_set_url_option(curl, azure_url);
-
-   curl_easy_setopt(curl, CURLOPT_READDATA, (void*) file);
-
-   curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)file_info.st_size);
-
-   res = curl_easy_perform(curl);
-   if (res != CURLE_OK)
+   // Send PUT request with file
+   res = pgmoneta_http_put_file(http, azure_host, azure_put_path, file, file_info.st_size, "application/octet-stream");
+   if (res != 0)
    {
-      fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
+      pgmoneta_log_error("Azure upload failed for %s", local_path);
       goto error;
    }
+   else
+   {
+      int status_code = 0;
+      if (http->headers && sscanf(http->headers, "HTTP/1.1 %d", &status_code) == 1)
+      {
+         pgmoneta_log_debug("Azure HTTP status code: %d", status_code);
+         if (status_code >= 200 && status_code < 300)
+         {
+            pgmoneta_log_info("Successfully uploaded file to Azure: %s", azure_path);
path: src/libpgmoneta/se_azure.c
user: login: jesperpedersen
body: _debug
created_at: 2025-05-06T14:08:54Z
author_association: MEMBER
diff_hunk: @@ -429,20 +412,30 @@ s3_send_upload_request(char* local_root, char* s3_root, char* relative_path)
       goto error;
    }
 
-   pgmoneta_http_set_request_option(curl, HTTP_PUT);
-
-   pgmoneta_http_set_url_option(curl, s3_url);
-
-   curl_easy_setopt(curl, CURLOPT_READDATA, (void*) file);
-
-   curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)file_info.st_size);
-
-   curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
-
-   res = curl_easy_perform(curl);
-   if (res != CURLE_OK)
+   // Perform the PUT request with file
+   res = pgmoneta_http_put_file(http, s3_host, s3_put_path, file, file_info.st_size, "application/octet-stream");
+   if (res == 0)
+   {
+      int status_code = 0;
+      if (http->headers && sscanf(http->headers, "HTTP/1.1 %d", &status_code) == 1)
+      {
+         printf("S3 HTTP status code: %d\n", status_code);
+         if (status_code >= 200 && status_code < 300)
+         {
+            printf("Successfully uploaded file to S3: %s\n", s3_path);
path: src/libpgmoneta/se_s3.c
user: login: jesperpedersen
body: _debug
created_at: 2025-05-06T14:09:57Z
author_association: MEMBER
diff_hunk: @@ -429,20 +412,30 @@ s3_send_upload_request(char* local_root, char* s3_root, char* relative_path)
       goto error;
    }
 
-   pgmoneta_http_set_request_option(curl, HTTP_PUT);
-
-   pgmoneta_http_set_url_option(curl, s3_url);
-
-   curl_easy_setopt(curl, CURLOPT_READDATA, (void*) file);
-
-   curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)file_info.st_size);
-
-   curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
-
-   res = curl_easy_perform(curl);
-   if (res != CURLE_OK)
+   // Perform the PUT request with file
+   res = pgmoneta_http_put_file(http, s3_host, s3_put_path, file, file_info.st_size, "application/octet-stream");
+   if (res == 0)
+   {
+      int status_code = 0;
+      if (http->headers && sscanf(http->headers, "HTTP/1.1 %d", &status_code) == 1)
+      {
+         printf("S3 HTTP status code: %d\n", status_code);
+         if (status_code >= 200 && status_code < 300)
+         {
+            printf("Successfully uploaded file to S3: %s\n", s3_path);
+            printf("Object URL: https://%s/%s\n", s3_host, s3_path);
+         }
+         else
+         {
+            fprintf(stderr, "S3 upload failed with status code: %d\n", status_code);
path: src/libpgmoneta/se_s3.c
user: login: jesperpedersen
body: More info, and _error
created_at: 2025-05-06T14:10:17Z
author_association: MEMBER
diff_hunk: @@ -429,20 +412,30 @@ s3_send_upload_request(char* local_root, char* s3_root, char* relative_path)
       goto error;
    }
 
-   pgmoneta_http_set_request_option(curl, HTTP_PUT);
-
-   pgmoneta_http_set_url_option(curl, s3_url);
-
-   curl_easy_setopt(curl, CURLOPT_READDATA, (void*) file);
-
-   curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)file_info.st_size);
-
-   curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
-
-   res = curl_easy_perform(curl);
-   if (res != CURLE_OK)
+   // Perform the PUT request with file
+   res = pgmoneta_http_put_file(http, s3_host, s3_put_path, file, file_info.st_size, "application/octet-stream");
+   if (res == 0)
+   {
+      int status_code = 0;
+      if (http->headers && sscanf(http->headers, "HTTP/1.1 %d", &status_code) == 1)
+      {
+         printf("S3 HTTP status code: %d\n", status_code);
+         if (status_code >= 200 && status_code < 300)
+         {
+            printf("Successfully uploaded file to S3: %s\n", s3_path);
+            printf("Object URL: https://%s/%s\n", s3_host, s3_path);
+         }
+         else
+         {
+            fprintf(stderr, "S3 upload failed with status code: %d\n", status_code);
+            fprintf(stderr, "Response headers: %s\n", http->headers);
path: src/libpgmoneta/se_s3.c
user: login: jesperpedersen
body: _debug
created_at: 2025-05-06T14:10:25Z
author_association: MEMBER
diff_hunk: @@ -84,6 +84,7 @@ static int get_permissions(char* from, int* permissions);
 
 static void do_copy_file(struct worker_common* wc);
 static void do_delete_file(struct worker_common* wc);
+bool pgmoneta_is_number(char* str, int base);
path: src/libpgmoneta/utils.c
user: login: jesperpedersen
body: No
created_at: 2025-05-06T14:10:48Z
author_association: MEMBER
diff_hunk: @@ -29,99 +29,906 @@
 /* pgmoneta */
 #include <pgmoneta.h>
 #include <http.h>
+#include <logging.h>
+#include <network.h>
+#include <security.h>
 #include <utils.h>
 
-struct curl_slist*
-pgmoneta_http_add_header(struct curl_slist* chunk, char* header, char* value)
+/* system */
+#include <errno.h>
+#include <openssl/err.h>
+#include <unistd.h>
+
+int pgmoneta_http_add_header(struct http* http, char* name, char* value);
path: src/libpgmoneta/http.c
user: login: jesperpedersen
body: I got to here...

Either they are in "public" API, and are called `pgmoneta_http_...` or they are static functions within the file called `http_...`
created_at: 2025-05-10T16:04:43Z
author_association: MEMBER
diff_hunk: @@ -323,26 +310,31 @@ azure_send_upload_request(char* local_root, char* azure_root, char* relative_pat
    char utc_date[UTC_TIME_LENGTH];
    char* string_to_sign = NULL;
    char* signing_key = NULL;
-   char* signature = NULL;
    char* base64_signature = NULL;
    size_t base64_signature_length;
    char* local_path = NULL;
    char* azure_path = NULL;
+   char azure_put_path[512];
path: src/libpgmoneta/se_azure.c
user: login: jesperpedersen
body: Use MAX_PATH
created_at: 2025-05-11T03:36:52Z
author_association: MEMBER
diff_hunk: @@ -367,16 +359,18 @@ azure_send_upload_request(char* local_root, char* azure_root, char* relative_pat
       goto error;
    }
 
-   // Construct string to sign.
+   // Since we specifiy octet-stream in header, we include it to our string_to_sign
    if (file_info.st_size == 0)
    {
-      string_to_sign = pgmoneta_append(string_to_sign, "PUT\n\n\n\n\n\n\n\n\n\n\n\nx-ms-blob-type:BlockBlob\nx-ms-date:");
+      string_to_sign = pgmoneta_append(string_to_sign, "PUT\n\n\n\n\napplication/octet-stream\n\n\n\n\n\n\nx-ms-blob-type:BlockBlob\nx-ms-date:");
    }
    else
    {
       string_to_sign = pgmoneta_append(string_to_sign, "PUT\n\n\n");
-      string_to_sign = pgmoneta_append(string_to_sign, (char*)file_info.st_size);
-      string_to_sign = pgmoneta_append(string_to_sign, "\n\n\n\n\n\n\n\n\nx-ms-blob-type:BlockBlob\nx-ms-date:");
+      char size_str[64];
path: src/libpgmoneta/se_azure.c
user: login: jesperpedersen
body: Move to top
created_at: 2025-05-11T03:37:20Z
author_association: MEMBER
diff_hunk: @@ -405,53 +399,83 @@ azure_send_upload_request(char* local_root, char* azure_root, char* relative_pat
    auth_value = pgmoneta_append(auth_value, ":");
    auth_value = pgmoneta_append(auth_value, base64_signature);
 
-   chunk = pgmoneta_http_add_header(chunk, "Authorization", auth_value);
-
-   chunk = pgmoneta_http_add_header(chunk, "x-ms-blob-type", "BlockBlob");
-
-   chunk = pgmoneta_http_add_header(chunk, "x-ms-date", utc_date);
-
-   chunk = pgmoneta_http_add_header(chunk, "x-ms-version", "2021-08-06");
+   azure_host = azure_get_host();
 
-   if (pgmoneta_http_set_header_option(curl, chunk))
+   // Create HTTP connection
+   if (pgmoneta_http_connect(azure_host, 443, true, &http))
    {
+      pgmoneta_log_error("Failed to connect to Azure host: %s", azure_host);
       goto error;
    }
 
-   azure_host = azure_get_host();
-
-   azure_url = pgmoneta_append(azure_url, "https://");
-   azure_url = pgmoneta_append(azure_url, azure_host);
-   azure_url = pgmoneta_append(azure_url, "/");
-   azure_url = pgmoneta_append(azure_url, azure_path);
-
-   pgmoneta_http_set_request_option(curl, HTTP_PUT);
-
-   pgmoneta_http_set_url_option(curl, azure_url);
+   // Add headers
+   pgmoneta_http_add_header(http, "Authorization", auth_value);
+   pgmoneta_http_add_header(http, "x-ms-blob-type", "BlockBlob");
+   pgmoneta_http_add_header(http, "x-ms-date", utc_date);
+   pgmoneta_http_add_header(http, "x-ms-version", "2021-08-06");
 
-   curl_easy_setopt(curl, CURLOPT_READDATA, (void*) file);
+   // Construct PUT path
+   snprintf(azure_put_path, sizeof(azure_put_path), "/%s/%s", config->azure_container, azure_path);
 
-   curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)file_info.st_size);
-
-   res = curl_easy_perform(curl);
-   if (res != CURLE_OK)
+   // Send PUT request with file
+   res = pgmoneta_http_put_file(http, azure_host, azure_put_path, file, file_info.st_size, "application/octet-stream");
+   if (res != 0)
    {
-      fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
+      pgmoneta_log_error("Azure upload failed for %s", local_path);
+      pgmoneta_log_error("Failed to execute HTTP PUT request");
       goto error;
    }
+   else
+   {
+      int status_code = 0;
+      if (http->headers && sscanf(http->headers, "HTTP/1.1 %d", &status_code) == 1)
+      {
+         pgmoneta_log_info("Azure HTTP status code: %d", status_code);
+         if (status_code >= 200 && status_code < 300)
+         {
+            pgmoneta_log_info("Successfully uploaded file to Azure: %s", azure_path);
+
+            char* azure_url = NULL;
+            azure_url = pgmoneta_append(azure_url, "https://");
+            azure_url = pgmoneta_append(azure_url, azure_host);
+            azure_url = pgmoneta_append(azure_url, "/");
+            azure_url = pgmoneta_append(azure_url, config->azure_container);
+            azure_url = pgmoneta_append(azure_url, "/");
+            azure_url = pgmoneta_append(azure_url, azure_path);
+
+            pgmoneta_log_info("Blob URL: %s", azure_url);
+            free(azure_url);
+         }
+         else
+         {
+            pgmoneta_log_error("Azure upload failed with status code: %d", status_code);
path: src/libpgmoneta/se_azure.c
user: login: jesperpedersen
body: Do one _error - otherwise the monitoring will register them as separate
created_at: 2025-05-11T03:38:18Z
author_association: MEMBER
diff_hunk: @@ -405,53 +399,83 @@ azure_send_upload_request(char* local_root, char* azure_root, char* relative_pat
    auth_value = pgmoneta_append(auth_value, ":");
    auth_value = pgmoneta_append(auth_value, base64_signature);
 
-   chunk = pgmoneta_http_add_header(chunk, "Authorization", auth_value);
-
-   chunk = pgmoneta_http_add_header(chunk, "x-ms-blob-type", "BlockBlob");
-
-   chunk = pgmoneta_http_add_header(chunk, "x-ms-date", utc_date);
-
-   chunk = pgmoneta_http_add_header(chunk, "x-ms-version", "2021-08-06");
+   azure_host = azure_get_host();
 
-   if (pgmoneta_http_set_header_option(curl, chunk))
+   // Create HTTP connection
+   if (pgmoneta_http_connect(azure_host, 443, true, &http))
    {
+      pgmoneta_log_error("Failed to connect to Azure host: %s", azure_host);
       goto error;
    }
 
-   azure_host = azure_get_host();
-
-   azure_url = pgmoneta_append(azure_url, "https://");
-   azure_url = pgmoneta_append(azure_url, azure_host);
-   azure_url = pgmoneta_append(azure_url, "/");
-   azure_url = pgmoneta_append(azure_url, azure_path);
-
-   pgmoneta_http_set_request_option(curl, HTTP_PUT);
-
-   pgmoneta_http_set_url_option(curl, azure_url);
+   // Add headers
+   pgmoneta_http_add_header(http, "Authorization", auth_value);
+   pgmoneta_http_add_header(http, "x-ms-blob-type", "BlockBlob");
+   pgmoneta_http_add_header(http, "x-ms-date", utc_date);
+   pgmoneta_http_add_header(http, "x-ms-version", "2021-08-06");
 
-   curl_easy_setopt(curl, CURLOPT_READDATA, (void*) file);
+   // Construct PUT path
+   snprintf(azure_put_path, sizeof(azure_put_path), "/%s/%s", config->azure_container, azure_path);
 
-   curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)file_info.st_size);
-
-   res = curl_easy_perform(curl);
-   if (res != CURLE_OK)
+   // Send PUT request with file
+   res = pgmoneta_http_put_file(http, azure_host, azure_put_path, file, file_info.st_size, "application/octet-stream");
+   if (res != 0)
    {
-      fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
+      pgmoneta_log_error("Azure upload failed for %s", local_path);
+      pgmoneta_log_error("Failed to execute HTTP PUT request");
       goto error;
    }
+   else
+   {
+      int status_code = 0;
+      if (http->headers && sscanf(http->headers, "HTTP/1.1 %d", &status_code) == 1)
+      {
+         pgmoneta_log_info("Azure HTTP status code: %d", status_code);
+         if (status_code >= 200 && status_code < 300)
+         {
+            pgmoneta_log_info("Successfully uploaded file to Azure: %s", azure_path);
+
+            char* azure_url = NULL;
+            azure_url = pgmoneta_append(azure_url, "https://");
+            azure_url = pgmoneta_append(azure_url, azure_host);
+            azure_url = pgmoneta_append(azure_url, "/");
+            azure_url = pgmoneta_append(azure_url, config->azure_container);
+            azure_url = pgmoneta_append(azure_url, "/");
+            azure_url = pgmoneta_append(azure_url, azure_path);
+
+            pgmoneta_log_info("Blob URL: %s", azure_url);
+            free(azure_url);
+         }
+         else
+         {
+            pgmoneta_log_error("Azure upload failed with status code: %d", status_code);
+            pgmoneta_log_error("Failed to upload: %s to Azure path: %s", local_path, azure_path);
+            pgmoneta_log_error("Azure container: %s, host: %s", config->azure_container, azure_host);
+            pgmoneta_log_error("Response headers: %s", http->headers ? http->headers : "None");
+            pgmoneta_log_error("Response body: %s", http->body ? http->body : "None");
+            goto error;
+         }
+      }
+      else
+      {
+         pgmoneta_log_error("Azure upload failed - could not parse HTTP status code");
path: src/libpgmoneta/se_azure.c
user: login: jesperpedersen
body: Same
created_at: 2025-05-11T03:38:26Z
author_association: MEMBER
diff_hunk: @@ -29,99 +29,898 @@
 /* pgmoneta */
 #include <pgmoneta.h>
 #include <http.h>
+#include <logging.h>
+#include <network.h>
+#include <security.h>
 #include <utils.h>
 
-struct curl_slist*
-pgmoneta_http_add_header(struct curl_slist* chunk, char* header, char* value)
+/* system */
+#include <errno.h>
+#include <openssl/err.h>
path: src/libpgmoneta/http.c
user: login: jesperpedersen
body: Its
```
#include <pgmoneta.h>
#include <pgmoneta files> -- sorted

#include <system files> -- sorted
#include <system files with directories> -- sorted
```

So `openssl/err.h` has to move down
created_at: 2025-05-11T03:41:10Z
author_association: MEMBER
diff_hunk: @@ -29,99 +29,898 @@
 /* pgmoneta */
 #include <pgmoneta.h>
 #include <http.h>
+#include <logging.h>
+#include <network.h>
+#include <security.h>
 #include <utils.h>
 
-struct curl_slist*
-pgmoneta_http_add_header(struct curl_slist* chunk, char* header, char* value)
+/* system */
+#include <errno.h>
+#include <openssl/err.h>
+#include <unistd.h>
+
+static int http_build_header(int method, char* path, char** request);
+static int http_extract_headers_body(char* response, struct http* http);
+
+int
+pgmoneta_http_add_header(struct http* http, char* name, char* value)
 {
-   char* h = NULL;
+   char* tmp;
 
-   h = pgmoneta_append(h, header);
-   h = pgmoneta_append(h, ": ");
-   h = pgmoneta_append(h, value);
+   tmp = pgmoneta_append(http->request_headers, name);
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   chunk = curl_slist_append(chunk, h);
+   tmp = pgmoneta_append(http->request_headers, ": ");
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
+
+   tmp = pgmoneta_append(http->request_headers, value);
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   free(h);
+   tmp = pgmoneta_append(http->request_headers, "\r\n");
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   return chunk;
+   return 0;
+}
+
+static int
+http_build_header(int method, char* path, char** request)
+{
+   char* r = NULL;
+   *request = NULL;
path: src/libpgmoneta/http.c
user: login: jesperpedersen
body: Add a newline above
created_at: 2025-05-11T03:41:39Z
author_association: MEMBER
diff_hunk: @@ -29,99 +29,898 @@
 /* pgmoneta */
 #include <pgmoneta.h>
 #include <http.h>
+#include <logging.h>
+#include <network.h>
+#include <security.h>
 #include <utils.h>
 
-struct curl_slist*
-pgmoneta_http_add_header(struct curl_slist* chunk, char* header, char* value)
+/* system */
+#include <errno.h>
+#include <openssl/err.h>
+#include <unistd.h>
+
+static int http_build_header(int method, char* path, char** request);
+static int http_extract_headers_body(char* response, struct http* http);
+
+int
+pgmoneta_http_add_header(struct http* http, char* name, char* value)
 {
-   char* h = NULL;
+   char* tmp;
 
-   h = pgmoneta_append(h, header);
-   h = pgmoneta_append(h, ": ");
-   h = pgmoneta_append(h, value);
+   tmp = pgmoneta_append(http->request_headers, name);
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   chunk = curl_slist_append(chunk, h);
+   tmp = pgmoneta_append(http->request_headers, ": ");
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
+
+   tmp = pgmoneta_append(http->request_headers, value);
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   free(h);
+   tmp = pgmoneta_append(http->request_headers, "\r\n");
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   return chunk;
+   return 0;
+}
+
+static int
+http_build_header(int method, char* path, char** request)
+{
+   char* r = NULL;
+   *request = NULL;
+
+   if (method == PGMONETA_HTTP_GET)
+   {
+      r = pgmoneta_append(r, "GET ");
+   }
+   else if (method == PGMONETA_HTTP_POST)
+   {
+      r = pgmoneta_append(r, "POST ");
+   }
+   else if (method == PGMONETA_HTTP_PUT)
+   {
+      r = pgmoneta_append(r, "PUT ");
+   }
+   else
+   {
+      pgmoneta_log_error("Invalid HTTP method: %d", method);
+      return 1;
+   }
+
+   r = pgmoneta_append(r, path);
+   r = pgmoneta_append(r, " HTTP/1.1\r\n");
+
+   *request = r;
+
+   return 0;
+}
+
+static int
+http_extract_headers_body(char* response, struct http* http)
+{
+   bool header = true;
+   char* p = NULL;
+   char* response_copy = NULL;
+
+   if (response == NULL)
+   {
+      pgmoneta_log_error("Response is NULL");
+      return 1;
+   }
+
+   response_copy = strdup(response);
+   if (response_copy == NULL)
+   {
+      pgmoneta_log_error("Failed to duplicate response string");
+      return 1;
+   }
+
+   p = strtok(response_copy, "\n");
+   while (p != NULL)
+   {
+      if (*p == '\r')
+      {
+         header = false;
+      }
+      else
+      {
+         if (!pgmoneta_is_number(p, 16))
+         {
+            if (header)
+            {
+               http->headers = pgmoneta_append(http->headers, p);
+               http->headers = pgmoneta_append_char(http->headers, '\n');
+            }
+            else
+            {
+               http->body = pgmoneta_append(http->body, p);
+               http->body = pgmoneta_append_char(http->body, '\n');
+            }
+         }
+      }
+
+      p = strtok(NULL, "\n");
+   }
+
+   free(response_copy);
+   return 0;
 }
 
 int
-pgmoneta_http_set_header_option(CURL* handle, struct curl_slist* chunk)
+pgmoneta_http_get(struct http* http, char* hostname, char* path)
 {
-   CURLcode res;
+   pgmoneta_log_debug("Starting pgmoneta_http_get");
path: src/libpgmoneta/http.c
user: login: jesperpedersen
body: This has to be after the variable definitions
created_at: 2025-05-11T03:46:00Z
author_association: MEMBER
diff_hunk: @@ -29,99 +29,898 @@
 /* pgmoneta */
 #include <pgmoneta.h>
 #include <http.h>
+#include <logging.h>
+#include <network.h>
+#include <security.h>
 #include <utils.h>
 
-struct curl_slist*
-pgmoneta_http_add_header(struct curl_slist* chunk, char* header, char* value)
+/* system */
+#include <errno.h>
+#include <openssl/err.h>
+#include <unistd.h>
+
+static int http_build_header(int method, char* path, char** request);
+static int http_extract_headers_body(char* response, struct http* http);
+
+int
+pgmoneta_http_add_header(struct http* http, char* name, char* value)
 {
-   char* h = NULL;
+   char* tmp;
 
-   h = pgmoneta_append(h, header);
-   h = pgmoneta_append(h, ": ");
-   h = pgmoneta_append(h, value);
+   tmp = pgmoneta_append(http->request_headers, name);
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   chunk = curl_slist_append(chunk, h);
+   tmp = pgmoneta_append(http->request_headers, ": ");
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
+
+   tmp = pgmoneta_append(http->request_headers, value);
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   free(h);
+   tmp = pgmoneta_append(http->request_headers, "\r\n");
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   return chunk;
+   return 0;
+}
+
+static int
+http_build_header(int method, char* path, char** request)
+{
+   char* r = NULL;
+   *request = NULL;
+
+   if (method == PGMONETA_HTTP_GET)
+   {
+      r = pgmoneta_append(r, "GET ");
+   }
+   else if (method == PGMONETA_HTTP_POST)
+   {
+      r = pgmoneta_append(r, "POST ");
+   }
+   else if (method == PGMONETA_HTTP_PUT)
+   {
+      r = pgmoneta_append(r, "PUT ");
+   }
+   else
+   {
+      pgmoneta_log_error("Invalid HTTP method: %d", method);
+      return 1;
+   }
+
+   r = pgmoneta_append(r, path);
+   r = pgmoneta_append(r, " HTTP/1.1\r\n");
+
+   *request = r;
+
+   return 0;
+}
+
+static int
+http_extract_headers_body(char* response, struct http* http)
+{
+   bool header = true;
+   char* p = NULL;
+   char* response_copy = NULL;
+
+   if (response == NULL)
+   {
+      pgmoneta_log_error("Response is NULL");
+      return 1;
+   }
+
+   response_copy = strdup(response);
+   if (response_copy == NULL)
+   {
+      pgmoneta_log_error("Failed to duplicate response string");
+      return 1;
+   }
+
+   p = strtok(response_copy, "\n");
+   while (p != NULL)
+   {
+      if (*p == '\r')
+      {
+         header = false;
+      }
+      else
+      {
+         if (!pgmoneta_is_number(p, 16))
+         {
+            if (header)
+            {
+               http->headers = pgmoneta_append(http->headers, p);
+               http->headers = pgmoneta_append_char(http->headers, '\n');
+            }
+            else
+            {
+               http->body = pgmoneta_append(http->body, p);
+               http->body = pgmoneta_append_char(http->body, '\n');
+            }
+         }
+      }
+
+      p = strtok(NULL, "\n");
+   }
+
+   free(response_copy);
+   return 0;
 }
 
 int
-pgmoneta_http_set_header_option(CURL* handle, struct curl_slist* chunk)
+pgmoneta_http_get(struct http* http, char* hostname, char* path)
 {
-   CURLcode res;
+   pgmoneta_log_debug("Starting pgmoneta_http_get");
+   struct message* msg_request = NULL;
+   int error = 0;
+   int status;
+   char* request = NULL;
+   char* full_request = NULL;
+   char* response = NULL;
+   char* user_agent = NULL;
+   char* endpoint = path ? path : "/get";
+
+   if (http_build_header(PGMONETA_HTTP_GET, endpoint, &request))
+   {
+      pgmoneta_log_error("Failed to build HTTP header");
+      goto error;
+   }
+
+   pgmoneta_http_add_header(http, "Host", hostname);
+   user_agent = pgmoneta_append(user_agent, "pgmoneta/");
+   user_agent = pgmoneta_append(user_agent, VERSION);
+   pgmoneta_http_add_header(http, "User-Agent", user_agent);
+   pgmoneta_http_add_header(http, "Accept", "text/*");
+   pgmoneta_http_add_header(http, "Connection", "close");
+
+   full_request = pgmoneta_append(NULL, request);
+   full_request = pgmoneta_append(full_request, http->request_headers);
+   full_request = pgmoneta_append(full_request, "\r\n");
+
+   msg_request = (struct message*)malloc(sizeof(struct message));
+   if (msg_request == NULL)
+   {
+      pgmoneta_log_error("Failed to allocate msg_request");
+      goto error;
+   }
+
+   memset(msg_request, 0, sizeof(struct message));
 
-   if (handle == NULL)
+   msg_request->data = full_request;
+   msg_request->length = strlen(full_request) + 1;
+
+   error = 0;
+req:
+   if (error < 5)
    {
+      status = pgmoneta_write_message(http->ssl, http->socket, msg_request);
+      if (status != MESSAGE_STATUS_OK)
+      {
+         error++;
+         pgmoneta_log_debug("Write failed, retrying (%d/5)", error);
+         goto req;
+      }
+   }
+   else
+   {
+      pgmoneta_log_error("Failed to write after 5 attempts");
       goto error;
    }
 
-   res = curl_easy_setopt(handle, CURLOPT_HTTPHEADER, chunk);
+   status = pgmoneta_http_read(http->ssl, http->socket, &response);
 
-   if (res != CURLE_OK)
+   if (response == NULL)
    {
+      pgmoneta_log_error("No response data collected");
       goto error;
    }
 
+   if (http_extract_headers_body(response, http))
+   {
+      pgmoneta_log_error("Failed to extract headers and body");
+      goto error;
+   }
+
+   pgmoneta_log_debug("HTTP Headers: %s", http->headers ? http->headers : "NULL");
+   pgmoneta_log_debug("HTTP Body: %s", http->body ? http->body : "NULL");
+
+   free(request);
+   free(full_request);
+   free(response);
+   free(msg_request);
+   free(user_agent);
+
+   free(http->request_headers);
+   http->request_headers = NULL;
+
    return 0;
 
 error:
+   pgmoneta_log_error("Error in pgmoneta_http_get, cleaning up");
path: src/libpgmoneta/http.c
user: login: jesperpedersen
body: One _error per failure - so remove
created_at: 2025-05-11T03:46:35Z
author_association: MEMBER
diff_hunk: @@ -29,99 +29,898 @@
 /* pgmoneta */
 #include <pgmoneta.h>
 #include <http.h>
+#include <logging.h>
+#include <network.h>
+#include <security.h>
 #include <utils.h>
 
-struct curl_slist*
-pgmoneta_http_add_header(struct curl_slist* chunk, char* header, char* value)
+/* system */
+#include <errno.h>
+#include <openssl/err.h>
+#include <unistd.h>
+
+static int http_build_header(int method, char* path, char** request);
+static int http_extract_headers_body(char* response, struct http* http);
+
+int
+pgmoneta_http_add_header(struct http* http, char* name, char* value)
 {
-   char* h = NULL;
+   char* tmp;
 
-   h = pgmoneta_append(h, header);
-   h = pgmoneta_append(h, ": ");
-   h = pgmoneta_append(h, value);
+   tmp = pgmoneta_append(http->request_headers, name);
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   chunk = curl_slist_append(chunk, h);
+   tmp = pgmoneta_append(http->request_headers, ": ");
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
+
+   tmp = pgmoneta_append(http->request_headers, value);
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   free(h);
+   tmp = pgmoneta_append(http->request_headers, "\r\n");
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   return chunk;
+   return 0;
+}
+
+static int
+http_build_header(int method, char* path, char** request)
+{
+   char* r = NULL;
+   *request = NULL;
+
+   if (method == PGMONETA_HTTP_GET)
+   {
+      r = pgmoneta_append(r, "GET ");
+   }
+   else if (method == PGMONETA_HTTP_POST)
+   {
+      r = pgmoneta_append(r, "POST ");
+   }
+   else if (method == PGMONETA_HTTP_PUT)
+   {
+      r = pgmoneta_append(r, "PUT ");
+   }
+   else
+   {
+      pgmoneta_log_error("Invalid HTTP method: %d", method);
+      return 1;
+   }
+
+   r = pgmoneta_append(r, path);
+   r = pgmoneta_append(r, " HTTP/1.1\r\n");
+
+   *request = r;
+
+   return 0;
+}
+
+static int
+http_extract_headers_body(char* response, struct http* http)
+{
+   bool header = true;
+   char* p = NULL;
+   char* response_copy = NULL;
+
+   if (response == NULL)
+   {
+      pgmoneta_log_error("Response is NULL");
+      return 1;
+   }
+
+   response_copy = strdup(response);
+   if (response_copy == NULL)
+   {
+      pgmoneta_log_error("Failed to duplicate response string");
+      return 1;
+   }
+
+   p = strtok(response_copy, "\n");
+   while (p != NULL)
+   {
+      if (*p == '\r')
+      {
+         header = false;
+      }
+      else
+      {
+         if (!pgmoneta_is_number(p, 16))
+         {
+            if (header)
+            {
+               http->headers = pgmoneta_append(http->headers, p);
+               http->headers = pgmoneta_append_char(http->headers, '\n');
+            }
+            else
+            {
+               http->body = pgmoneta_append(http->body, p);
+               http->body = pgmoneta_append_char(http->body, '\n');
+            }
+         }
+      }
+
+      p = strtok(NULL, "\n");
+   }
+
+   free(response_copy);
+   return 0;
 }
 
 int
-pgmoneta_http_set_header_option(CURL* handle, struct curl_slist* chunk)
+pgmoneta_http_get(struct http* http, char* hostname, char* path)
 {
-   CURLcode res;
+   pgmoneta_log_debug("Starting pgmoneta_http_get");
+   struct message* msg_request = NULL;
+   int error = 0;
+   int status;
+   char* request = NULL;
+   char* full_request = NULL;
+   char* response = NULL;
+   char* user_agent = NULL;
+   char* endpoint = path ? path : "/get";
+
+   if (http_build_header(PGMONETA_HTTP_GET, endpoint, &request))
+   {
+      pgmoneta_log_error("Failed to build HTTP header");
+      goto error;
+   }
+
+   pgmoneta_http_add_header(http, "Host", hostname);
+   user_agent = pgmoneta_append(user_agent, "pgmoneta/");
+   user_agent = pgmoneta_append(user_agent, VERSION);
+   pgmoneta_http_add_header(http, "User-Agent", user_agent);
+   pgmoneta_http_add_header(http, "Accept", "text/*");
+   pgmoneta_http_add_header(http, "Connection", "close");
+
+   full_request = pgmoneta_append(NULL, request);
+   full_request = pgmoneta_append(full_request, http->request_headers);
+   full_request = pgmoneta_append(full_request, "\r\n");
+
+   msg_request = (struct message*)malloc(sizeof(struct message));
+   if (msg_request == NULL)
+   {
+      pgmoneta_log_error("Failed to allocate msg_request");
+      goto error;
+   }
+
+   memset(msg_request, 0, sizeof(struct message));
 
-   if (handle == NULL)
+   msg_request->data = full_request;
+   msg_request->length = strlen(full_request) + 1;
+
+   error = 0;
+req:
+   if (error < 5)
    {
+      status = pgmoneta_write_message(http->ssl, http->socket, msg_request);
+      if (status != MESSAGE_STATUS_OK)
+      {
+         error++;
+         pgmoneta_log_debug("Write failed, retrying (%d/5)", error);
+         goto req;
+      }
+   }
+   else
+   {
+      pgmoneta_log_error("Failed to write after 5 attempts");
       goto error;
    }
 
-   res = curl_easy_setopt(handle, CURLOPT_HTTPHEADER, chunk);
+   status = pgmoneta_http_read(http->ssl, http->socket, &response);
 
-   if (res != CURLE_OK)
+   if (response == NULL)
    {
+      pgmoneta_log_error("No response data collected");
       goto error;
    }
 
+   if (http_extract_headers_body(response, http))
+   {
+      pgmoneta_log_error("Failed to extract headers and body");
+      goto error;
+   }
+
+   pgmoneta_log_debug("HTTP Headers: %s", http->headers ? http->headers : "NULL");
+   pgmoneta_log_debug("HTTP Body: %s", http->body ? http->body : "NULL");
+
+   free(request);
+   free(full_request);
+   free(response);
+   free(msg_request);
+   free(user_agent);
+
+   free(http->request_headers);
+   http->request_headers = NULL;
+
    return 0;
 
 error:
+   pgmoneta_log_error("Error in pgmoneta_http_get, cleaning up");
+   if (request)
path: src/libpgmoneta/http.c
user: login: jesperpedersen
body: `free()` works on a NULL pointer - so we can remove the guards
created_at: 2025-05-11T03:47:20Z
author_association: MEMBER
diff_hunk: @@ -29,99 +29,898 @@
 /* pgmoneta */
 #include <pgmoneta.h>
 #include <http.h>
+#include <logging.h>
+#include <network.h>
+#include <security.h>
 #include <utils.h>
 
-struct curl_slist*
-pgmoneta_http_add_header(struct curl_slist* chunk, char* header, char* value)
+/* system */
+#include <errno.h>
+#include <openssl/err.h>
+#include <unistd.h>
+
+static int http_build_header(int method, char* path, char** request);
+static int http_extract_headers_body(char* response, struct http* http);
+
+int
+pgmoneta_http_add_header(struct http* http, char* name, char* value)
 {
-   char* h = NULL;
+   char* tmp;
 
-   h = pgmoneta_append(h, header);
-   h = pgmoneta_append(h, ": ");
-   h = pgmoneta_append(h, value);
+   tmp = pgmoneta_append(http->request_headers, name);
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   chunk = curl_slist_append(chunk, h);
+   tmp = pgmoneta_append(http->request_headers, ": ");
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
+
+   tmp = pgmoneta_append(http->request_headers, value);
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   free(h);
+   tmp = pgmoneta_append(http->request_headers, "\r\n");
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   return chunk;
+   return 0;
+}
+
+static int
+http_build_header(int method, char* path, char** request)
+{
+   char* r = NULL;
+   *request = NULL;
+
+   if (method == PGMONETA_HTTP_GET)
+   {
+      r = pgmoneta_append(r, "GET ");
+   }
+   else if (method == PGMONETA_HTTP_POST)
+   {
+      r = pgmoneta_append(r, "POST ");
+   }
+   else if (method == PGMONETA_HTTP_PUT)
+   {
+      r = pgmoneta_append(r, "PUT ");
+   }
+   else
+   {
+      pgmoneta_log_error("Invalid HTTP method: %d", method);
+      return 1;
+   }
+
+   r = pgmoneta_append(r, path);
+   r = pgmoneta_append(r, " HTTP/1.1\r\n");
+
+   *request = r;
+
+   return 0;
+}
+
+static int
+http_extract_headers_body(char* response, struct http* http)
+{
+   bool header = true;
+   char* p = NULL;
+   char* response_copy = NULL;
+
+   if (response == NULL)
+   {
+      pgmoneta_log_error("Response is NULL");
+      return 1;
+   }
+
+   response_copy = strdup(response);
+   if (response_copy == NULL)
+   {
+      pgmoneta_log_error("Failed to duplicate response string");
+      return 1;
+   }
+
+   p = strtok(response_copy, "\n");
+   while (p != NULL)
+   {
+      if (*p == '\r')
+      {
+         header = false;
+      }
+      else
+      {
+         if (!pgmoneta_is_number(p, 16))
+         {
+            if (header)
+            {
+               http->headers = pgmoneta_append(http->headers, p);
+               http->headers = pgmoneta_append_char(http->headers, '\n');
+            }
+            else
+            {
+               http->body = pgmoneta_append(http->body, p);
+               http->body = pgmoneta_append_char(http->body, '\n');
+            }
+         }
+      }
+
+      p = strtok(NULL, "\n");
+   }
+
+   free(response_copy);
+   return 0;
 }
 
 int
-pgmoneta_http_set_header_option(CURL* handle, struct curl_slist* chunk)
+pgmoneta_http_get(struct http* http, char* hostname, char* path)
 {
-   CURLcode res;
+   pgmoneta_log_debug("Starting pgmoneta_http_get");
+   struct message* msg_request = NULL;
+   int error = 0;
+   int status;
+   char* request = NULL;
+   char* full_request = NULL;
+   char* response = NULL;
+   char* user_agent = NULL;
+   char* endpoint = path ? path : "/get";
+
+   if (http_build_header(PGMONETA_HTTP_GET, endpoint, &request))
+   {
+      pgmoneta_log_error("Failed to build HTTP header");
+      goto error;
+   }
+
+   pgmoneta_http_add_header(http, "Host", hostname);
+   user_agent = pgmoneta_append(user_agent, "pgmoneta/");
+   user_agent = pgmoneta_append(user_agent, VERSION);
+   pgmoneta_http_add_header(http, "User-Agent", user_agent);
+   pgmoneta_http_add_header(http, "Accept", "text/*");
+   pgmoneta_http_add_header(http, "Connection", "close");
+
+   full_request = pgmoneta_append(NULL, request);
+   full_request = pgmoneta_append(full_request, http->request_headers);
+   full_request = pgmoneta_append(full_request, "\r\n");
+
+   msg_request = (struct message*)malloc(sizeof(struct message));
+   if (msg_request == NULL)
+   {
+      pgmoneta_log_error("Failed to allocate msg_request");
+      goto error;
+   }
+
+   memset(msg_request, 0, sizeof(struct message));
 
-   if (handle == NULL)
+   msg_request->data = full_request;
+   msg_request->length = strlen(full_request) + 1;
+
+   error = 0;
+req:
+   if (error < 5)
    {
+      status = pgmoneta_write_message(http->ssl, http->socket, msg_request);
+      if (status != MESSAGE_STATUS_OK)
+      {
+         error++;
+         pgmoneta_log_debug("Write failed, retrying (%d/5)", error);
+         goto req;
+      }
+   }
+   else
+   {
+      pgmoneta_log_error("Failed to write after 5 attempts");
       goto error;
    }
 
-   res = curl_easy_setopt(handle, CURLOPT_HTTPHEADER, chunk);
+   status = pgmoneta_http_read(http->ssl, http->socket, &response);
 
-   if (res != CURLE_OK)
+   if (response == NULL)
    {
+      pgmoneta_log_error("No response data collected");
       goto error;
    }
 
+   if (http_extract_headers_body(response, http))
+   {
+      pgmoneta_log_error("Failed to extract headers and body");
+      goto error;
+   }
+
+   pgmoneta_log_debug("HTTP Headers: %s", http->headers ? http->headers : "NULL");
+   pgmoneta_log_debug("HTTP Body: %s", http->body ? http->body : "NULL");
+
+   free(request);
+   free(full_request);
+   free(response);
+   free(msg_request);
+   free(user_agent);
+
+   free(http->request_headers);
+   http->request_headers = NULL;
+
    return 0;
 
 error:
+   pgmoneta_log_error("Error in pgmoneta_http_get, cleaning up");
+   if (request)
+   {
+      free(request);
+   }
+   if (full_request)
+   {
+      free(full_request);
+   }
+   if (response)
+   {
+      free(response);
+   }
+   if (msg_request)
+   {
+      free(msg_request);
+   }
+   if (user_agent)
+   {
+      free(user_agent);
+   }
+
+   free(http->request_headers);
+   http->request_headers = NULL;
 
    return 1;
 }
 
 int
-pgmoneta_http_set_request_option(CURL* handle, bool request_type)
+pgmoneta_http_connect(char* hostname, int port, bool secure, struct http** result)
+{
+   pgmoneta_log_debug("Connecting to %s:%d (secure: %d)", hostname, port, secure);
path: src/libpgmoneta/http.c
user: login: jesperpedersen
body: Same
created_at: 2025-05-11T03:47:28Z
author_association: MEMBER
diff_hunk: @@ -29,99 +29,898 @@
 /* pgmoneta */
 #include <pgmoneta.h>
 #include <http.h>
+#include <logging.h>
+#include <network.h>
+#include <security.h>
 #include <utils.h>
 
-struct curl_slist*
-pgmoneta_http_add_header(struct curl_slist* chunk, char* header, char* value)
+/* system */
+#include <errno.h>
+#include <openssl/err.h>
+#include <unistd.h>
+
+static int http_build_header(int method, char* path, char** request);
+static int http_extract_headers_body(char* response, struct http* http);
+
+int
+pgmoneta_http_add_header(struct http* http, char* name, char* value)
 {
-   char* h = NULL;
+   char* tmp;
 
-   h = pgmoneta_append(h, header);
-   h = pgmoneta_append(h, ": ");
-   h = pgmoneta_append(h, value);
+   tmp = pgmoneta_append(http->request_headers, name);
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   chunk = curl_slist_append(chunk, h);
+   tmp = pgmoneta_append(http->request_headers, ": ");
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
+
+   tmp = pgmoneta_append(http->request_headers, value);
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   free(h);
+   tmp = pgmoneta_append(http->request_headers, "\r\n");
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   return chunk;
+   return 0;
+}
+
+static int
+http_build_header(int method, char* path, char** request)
+{
+   char* r = NULL;
+   *request = NULL;
+
+   if (method == PGMONETA_HTTP_GET)
+   {
+      r = pgmoneta_append(r, "GET ");
+   }
+   else if (method == PGMONETA_HTTP_POST)
+   {
+      r = pgmoneta_append(r, "POST ");
+   }
+   else if (method == PGMONETA_HTTP_PUT)
+   {
+      r = pgmoneta_append(r, "PUT ");
+   }
+   else
+   {
+      pgmoneta_log_error("Invalid HTTP method: %d", method);
+      return 1;
+   }
+
+   r = pgmoneta_append(r, path);
+   r = pgmoneta_append(r, " HTTP/1.1\r\n");
+
+   *request = r;
+
+   return 0;
+}
+
+static int
+http_extract_headers_body(char* response, struct http* http)
+{
+   bool header = true;
+   char* p = NULL;
+   char* response_copy = NULL;
+
+   if (response == NULL)
+   {
+      pgmoneta_log_error("Response is NULL");
+      return 1;
+   }
+
+   response_copy = strdup(response);
+   if (response_copy == NULL)
+   {
+      pgmoneta_log_error("Failed to duplicate response string");
+      return 1;
+   }
+
+   p = strtok(response_copy, "\n");
+   while (p != NULL)
+   {
+      if (*p == '\r')
+      {
+         header = false;
+      }
+      else
+      {
+         if (!pgmoneta_is_number(p, 16))
+         {
+            if (header)
+            {
+               http->headers = pgmoneta_append(http->headers, p);
+               http->headers = pgmoneta_append_char(http->headers, '\n');
+            }
+            else
+            {
+               http->body = pgmoneta_append(http->body, p);
+               http->body = pgmoneta_append_char(http->body, '\n');
+            }
+         }
+      }
+
+      p = strtok(NULL, "\n");
+   }
+
+   free(response_copy);
+   return 0;
 }
 
 int
-pgmoneta_http_set_header_option(CURL* handle, struct curl_slist* chunk)
+pgmoneta_http_get(struct http* http, char* hostname, char* path)
 {
-   CURLcode res;
+   pgmoneta_log_debug("Starting pgmoneta_http_get");
+   struct message* msg_request = NULL;
+   int error = 0;
+   int status;
+   char* request = NULL;
+   char* full_request = NULL;
+   char* response = NULL;
+   char* user_agent = NULL;
+   char* endpoint = path ? path : "/get";
+
+   if (http_build_header(PGMONETA_HTTP_GET, endpoint, &request))
+   {
+      pgmoneta_log_error("Failed to build HTTP header");
+      goto error;
+   }
+
+   pgmoneta_http_add_header(http, "Host", hostname);
+   user_agent = pgmoneta_append(user_agent, "pgmoneta/");
+   user_agent = pgmoneta_append(user_agent, VERSION);
+   pgmoneta_http_add_header(http, "User-Agent", user_agent);
+   pgmoneta_http_add_header(http, "Accept", "text/*");
+   pgmoneta_http_add_header(http, "Connection", "close");
+
+   full_request = pgmoneta_append(NULL, request);
+   full_request = pgmoneta_append(full_request, http->request_headers);
+   full_request = pgmoneta_append(full_request, "\r\n");
+
+   msg_request = (struct message*)malloc(sizeof(struct message));
+   if (msg_request == NULL)
+   {
+      pgmoneta_log_error("Failed to allocate msg_request");
+      goto error;
+   }
+
+   memset(msg_request, 0, sizeof(struct message));
 
-   if (handle == NULL)
+   msg_request->data = full_request;
+   msg_request->length = strlen(full_request) + 1;
+
+   error = 0;
+req:
+   if (error < 5)
    {
+      status = pgmoneta_write_message(http->ssl, http->socket, msg_request);
+      if (status != MESSAGE_STATUS_OK)
+      {
+         error++;
+         pgmoneta_log_debug("Write failed, retrying (%d/5)", error);
+         goto req;
+      }
+   }
+   else
+   {
+      pgmoneta_log_error("Failed to write after 5 attempts");
       goto error;
    }
 
-   res = curl_easy_setopt(handle, CURLOPT_HTTPHEADER, chunk);
+   status = pgmoneta_http_read(http->ssl, http->socket, &response);
 
-   if (res != CURLE_OK)
+   if (response == NULL)
    {
+      pgmoneta_log_error("No response data collected");
       goto error;
    }
 
+   if (http_extract_headers_body(response, http))
+   {
+      pgmoneta_log_error("Failed to extract headers and body");
+      goto error;
+   }
+
+   pgmoneta_log_debug("HTTP Headers: %s", http->headers ? http->headers : "NULL");
+   pgmoneta_log_debug("HTTP Body: %s", http->body ? http->body : "NULL");
+
+   free(request);
+   free(full_request);
+   free(response);
+   free(msg_request);
+   free(user_agent);
+
+   free(http->request_headers);
+   http->request_headers = NULL;
+
    return 0;
 
 error:
+   pgmoneta_log_error("Error in pgmoneta_http_get, cleaning up");
+   if (request)
+   {
+      free(request);
+   }
+   if (full_request)
+   {
+      free(full_request);
+   }
+   if (response)
+   {
+      free(response);
+   }
+   if (msg_request)
+   {
+      free(msg_request);
+   }
+   if (user_agent)
+   {
+      free(user_agent);
+   }
+
+   free(http->request_headers);
+   http->request_headers = NULL;
 
    return 1;
 }
 
 int
-pgmoneta_http_set_request_option(CURL* handle, bool request_type)
+pgmoneta_http_connect(char* hostname, int port, bool secure, struct http** result)
+{
+   pgmoneta_log_debug("Connecting to %s:%d (secure: %d)", hostname, port, secure);
+   struct http* h = NULL;
+   int socket_fd = -1;
+   SSL* ssl = NULL;
+
+   h = (struct http*) malloc(sizeof(struct http));
+   if (h == NULL)
+   {
+      pgmoneta_log_error("Failed to allocate HTTP structure");
+      return 1;
+   }
+
+   memset(h, 0, sizeof(struct http));
+
+   if (pgmoneta_connect(hostname, port, &socket_fd))
+   {
+      pgmoneta_log_error("Failed to connect to %s:%d", hostname, port);
+      free(h);
+      return 1;
+   }
+
+   h->socket = socket_fd;
+
+   if (secure)
+   {
+      SSL_CTX* ctx = NULL;
+
+      if (pgmoneta_create_ssl_ctx(true, &ctx))
+      {
+         pgmoneta_log_error("Failed to create SSL context");
path: src/libpgmoneta/http.c
user: login: jesperpedersen
body: Do an `error:` label
created_at: 2025-05-11T03:47:56Z
author_association: MEMBER
diff_hunk: @@ -29,99 +29,898 @@
 /* pgmoneta */
 #include <pgmoneta.h>
 #include <http.h>
+#include <logging.h>
+#include <network.h>
+#include <security.h>
 #include <utils.h>
 
-struct curl_slist*
-pgmoneta_http_add_header(struct curl_slist* chunk, char* header, char* value)
+/* system */
+#include <errno.h>
+#include <openssl/err.h>
+#include <unistd.h>
+
+static int http_build_header(int method, char* path, char** request);
+static int http_extract_headers_body(char* response, struct http* http);
+
+int
+pgmoneta_http_add_header(struct http* http, char* name, char* value)
 {
-   char* h = NULL;
+   char* tmp;
 
-   h = pgmoneta_append(h, header);
-   h = pgmoneta_append(h, ": ");
-   h = pgmoneta_append(h, value);
+   tmp = pgmoneta_append(http->request_headers, name);
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   chunk = curl_slist_append(chunk, h);
+   tmp = pgmoneta_append(http->request_headers, ": ");
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
+
+   tmp = pgmoneta_append(http->request_headers, value);
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   free(h);
+   tmp = pgmoneta_append(http->request_headers, "\r\n");
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   return chunk;
+   return 0;
+}
+
+static int
+http_build_header(int method, char* path, char** request)
+{
+   char* r = NULL;
+   *request = NULL;
+
+   if (method == PGMONETA_HTTP_GET)
+   {
+      r = pgmoneta_append(r, "GET ");
+   }
+   else if (method == PGMONETA_HTTP_POST)
+   {
+      r = pgmoneta_append(r, "POST ");
+   }
+   else if (method == PGMONETA_HTTP_PUT)
+   {
+      r = pgmoneta_append(r, "PUT ");
+   }
+   else
+   {
+      pgmoneta_log_error("Invalid HTTP method: %d", method);
+      return 1;
+   }
+
+   r = pgmoneta_append(r, path);
+   r = pgmoneta_append(r, " HTTP/1.1\r\n");
+
+   *request = r;
+
+   return 0;
+}
+
+static int
+http_extract_headers_body(char* response, struct http* http)
+{
+   bool header = true;
+   char* p = NULL;
+   char* response_copy = NULL;
+
+   if (response == NULL)
+   {
+      pgmoneta_log_error("Response is NULL");
+      return 1;
+   }
+
+   response_copy = strdup(response);
+   if (response_copy == NULL)
+   {
+      pgmoneta_log_error("Failed to duplicate response string");
+      return 1;
+   }
+
+   p = strtok(response_copy, "\n");
+   while (p != NULL)
+   {
+      if (*p == '\r')
+      {
+         header = false;
+      }
+      else
+      {
+         if (!pgmoneta_is_number(p, 16))
+         {
+            if (header)
+            {
+               http->headers = pgmoneta_append(http->headers, p);
+               http->headers = pgmoneta_append_char(http->headers, '\n');
+            }
+            else
+            {
+               http->body = pgmoneta_append(http->body, p);
+               http->body = pgmoneta_append_char(http->body, '\n');
+            }
+         }
+      }
+
+      p = strtok(NULL, "\n");
+   }
+
+   free(response_copy);
+   return 0;
 }
 
 int
-pgmoneta_http_set_header_option(CURL* handle, struct curl_slist* chunk)
+pgmoneta_http_get(struct http* http, char* hostname, char* path)
 {
-   CURLcode res;
+   pgmoneta_log_debug("Starting pgmoneta_http_get");
+   struct message* msg_request = NULL;
+   int error = 0;
+   int status;
+   char* request = NULL;
+   char* full_request = NULL;
+   char* response = NULL;
+   char* user_agent = NULL;
+   char* endpoint = path ? path : "/get";
+
+   if (http_build_header(PGMONETA_HTTP_GET, endpoint, &request))
+   {
+      pgmoneta_log_error("Failed to build HTTP header");
+      goto error;
+   }
+
+   pgmoneta_http_add_header(http, "Host", hostname);
+   user_agent = pgmoneta_append(user_agent, "pgmoneta/");
+   user_agent = pgmoneta_append(user_agent, VERSION);
+   pgmoneta_http_add_header(http, "User-Agent", user_agent);
+   pgmoneta_http_add_header(http, "Accept", "text/*");
+   pgmoneta_http_add_header(http, "Connection", "close");
+
+   full_request = pgmoneta_append(NULL, request);
+   full_request = pgmoneta_append(full_request, http->request_headers);
+   full_request = pgmoneta_append(full_request, "\r\n");
+
+   msg_request = (struct message*)malloc(sizeof(struct message));
+   if (msg_request == NULL)
+   {
+      pgmoneta_log_error("Failed to allocate msg_request");
+      goto error;
+   }
+
+   memset(msg_request, 0, sizeof(struct message));
 
-   if (handle == NULL)
+   msg_request->data = full_request;
+   msg_request->length = strlen(full_request) + 1;
+
+   error = 0;
+req:
+   if (error < 5)
    {
+      status = pgmoneta_write_message(http->ssl, http->socket, msg_request);
+      if (status != MESSAGE_STATUS_OK)
+      {
+         error++;
+         pgmoneta_log_debug("Write failed, retrying (%d/5)", error);
+         goto req;
+      }
+   }
+   else
+   {
+      pgmoneta_log_error("Failed to write after 5 attempts");
       goto error;
    }
 
-   res = curl_easy_setopt(handle, CURLOPT_HTTPHEADER, chunk);
+   status = pgmoneta_http_read(http->ssl, http->socket, &response);
 
-   if (res != CURLE_OK)
+   if (response == NULL)
    {
+      pgmoneta_log_error("No response data collected");
       goto error;
    }
 
+   if (http_extract_headers_body(response, http))
+   {
+      pgmoneta_log_error("Failed to extract headers and body");
+      goto error;
+   }
+
+   pgmoneta_log_debug("HTTP Headers: %s", http->headers ? http->headers : "NULL");
+   pgmoneta_log_debug("HTTP Body: %s", http->body ? http->body : "NULL");
+
+   free(request);
+   free(full_request);
+   free(response);
+   free(msg_request);
+   free(user_agent);
+
+   free(http->request_headers);
+   http->request_headers = NULL;
+
    return 0;
 
 error:
+   pgmoneta_log_error("Error in pgmoneta_http_get, cleaning up");
+   if (request)
+   {
+      free(request);
+   }
+   if (full_request)
+   {
+      free(full_request);
+   }
+   if (response)
+   {
+      free(response);
+   }
+   if (msg_request)
+   {
+      free(msg_request);
+   }
+   if (user_agent)
+   {
+      free(user_agent);
+   }
+
+   free(http->request_headers);
+   http->request_headers = NULL;
 
    return 1;
 }
 
 int
-pgmoneta_http_set_request_option(CURL* handle, bool request_type)
+pgmoneta_http_connect(char* hostname, int port, bool secure, struct http** result)
+{
+   pgmoneta_log_debug("Connecting to %s:%d (secure: %d)", hostname, port, secure);
+   struct http* h = NULL;
+   int socket_fd = -1;
+   SSL* ssl = NULL;
+
+   h = (struct http*) malloc(sizeof(struct http));
+   if (h == NULL)
+   {
+      pgmoneta_log_error("Failed to allocate HTTP structure");
+      return 1;
+   }
+
+   memset(h, 0, sizeof(struct http));
+
+   if (pgmoneta_connect(hostname, port, &socket_fd))
+   {
+      pgmoneta_log_error("Failed to connect to %s:%d", hostname, port);
+      free(h);
+      return 1;
+   }
+
+   h->socket = socket_fd;
+
+   if (secure)
+   {
+      SSL_CTX* ctx = NULL;
+
+      if (pgmoneta_create_ssl_ctx(true, &ctx))
+      {
+         pgmoneta_log_error("Failed to create SSL context");
+         pgmoneta_disconnect(socket_fd);
+         free(h);
+         return 1;
+      }
+
+      if (SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION) == 0)
+      {
+         pgmoneta_log_error("Failed to set minimum TLS version");
+         SSL_CTX_free(ctx);
+         pgmoneta_disconnect(socket_fd);
+         free(h);
+         return 1;
+      }
+
+      ssl = SSL_new(ctx);
+      if (ssl == NULL)
+      {
+         pgmoneta_log_error("Failed to create SSL structure");
+         SSL_CTX_free(ctx);
+         pgmoneta_disconnect(socket_fd);
+         free(h);
+         return 1;
+      }
+
+      if (SSL_set_fd(ssl, socket_fd) == 0)
+      {
+         pgmoneta_log_error("Failed to set SSL file descriptor");
+         SSL_free(ssl);
path: src/libpgmoneta/http.c
user: login: jesperpedersen
body: So these 3 need to within the `error:`
created_at: 2025-05-11T03:48:32Z
author_association: MEMBER
diff_hunk: @@ -29,99 +29,898 @@
 /* pgmoneta */
 #include <pgmoneta.h>
 #include <http.h>
+#include <logging.h>
+#include <network.h>
+#include <security.h>
 #include <utils.h>
 
-struct curl_slist*
-pgmoneta_http_add_header(struct curl_slist* chunk, char* header, char* value)
+/* system */
+#include <errno.h>
+#include <openssl/err.h>
+#include <unistd.h>
+
+static int http_build_header(int method, char* path, char** request);
+static int http_extract_headers_body(char* response, struct http* http);
+
+int
+pgmoneta_http_add_header(struct http* http, char* name, char* value)
 {
-   char* h = NULL;
+   char* tmp;
 
-   h = pgmoneta_append(h, header);
-   h = pgmoneta_append(h, ": ");
-   h = pgmoneta_append(h, value);
+   tmp = pgmoneta_append(http->request_headers, name);
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   chunk = curl_slist_append(chunk, h);
+   tmp = pgmoneta_append(http->request_headers, ": ");
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
+
+   tmp = pgmoneta_append(http->request_headers, value);
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   free(h);
+   tmp = pgmoneta_append(http->request_headers, "\r\n");
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   return chunk;
+   return 0;
+}
+
+static int
+http_build_header(int method, char* path, char** request)
+{
+   char* r = NULL;
+   *request = NULL;
+
+   if (method == PGMONETA_HTTP_GET)
+   {
+      r = pgmoneta_append(r, "GET ");
+   }
+   else if (method == PGMONETA_HTTP_POST)
+   {
+      r = pgmoneta_append(r, "POST ");
+   }
+   else if (method == PGMONETA_HTTP_PUT)
+   {
+      r = pgmoneta_append(r, "PUT ");
+   }
+   else
+   {
+      pgmoneta_log_error("Invalid HTTP method: %d", method);
+      return 1;
+   }
+
+   r = pgmoneta_append(r, path);
+   r = pgmoneta_append(r, " HTTP/1.1\r\n");
+
+   *request = r;
+
+   return 0;
+}
+
+static int
+http_extract_headers_body(char* response, struct http* http)
+{
+   bool header = true;
+   char* p = NULL;
+   char* response_copy = NULL;
+
+   if (response == NULL)
+   {
+      pgmoneta_log_error("Response is NULL");
+      return 1;
+   }
+
+   response_copy = strdup(response);
+   if (response_copy == NULL)
+   {
+      pgmoneta_log_error("Failed to duplicate response string");
+      return 1;
+   }
+
+   p = strtok(response_copy, "\n");
+   while (p != NULL)
+   {
+      if (*p == '\r')
+      {
+         header = false;
+      }
+      else
+      {
+         if (!pgmoneta_is_number(p, 16))
+         {
+            if (header)
+            {
+               http->headers = pgmoneta_append(http->headers, p);
+               http->headers = pgmoneta_append_char(http->headers, '\n');
+            }
+            else
+            {
+               http->body = pgmoneta_append(http->body, p);
+               http->body = pgmoneta_append_char(http->body, '\n');
+            }
+         }
+      }
+
+      p = strtok(NULL, "\n");
+   }
+
+   free(response_copy);
+   return 0;
 }
 
 int
-pgmoneta_http_set_header_option(CURL* handle, struct curl_slist* chunk)
+pgmoneta_http_get(struct http* http, char* hostname, char* path)
 {
-   CURLcode res;
+   pgmoneta_log_debug("Starting pgmoneta_http_get");
+   struct message* msg_request = NULL;
+   int error = 0;
+   int status;
+   char* request = NULL;
+   char* full_request = NULL;
+   char* response = NULL;
+   char* user_agent = NULL;
+   char* endpoint = path ? path : "/get";
+
+   if (http_build_header(PGMONETA_HTTP_GET, endpoint, &request))
+   {
+      pgmoneta_log_error("Failed to build HTTP header");
+      goto error;
+   }
+
+   pgmoneta_http_add_header(http, "Host", hostname);
+   user_agent = pgmoneta_append(user_agent, "pgmoneta/");
+   user_agent = pgmoneta_append(user_agent, VERSION);
+   pgmoneta_http_add_header(http, "User-Agent", user_agent);
+   pgmoneta_http_add_header(http, "Accept", "text/*");
+   pgmoneta_http_add_header(http, "Connection", "close");
+
+   full_request = pgmoneta_append(NULL, request);
+   full_request = pgmoneta_append(full_request, http->request_headers);
+   full_request = pgmoneta_append(full_request, "\r\n");
+
+   msg_request = (struct message*)malloc(sizeof(struct message));
+   if (msg_request == NULL)
+   {
+      pgmoneta_log_error("Failed to allocate msg_request");
+      goto error;
+   }
+
+   memset(msg_request, 0, sizeof(struct message));
 
-   if (handle == NULL)
+   msg_request->data = full_request;
+   msg_request->length = strlen(full_request) + 1;
+
+   error = 0;
+req:
+   if (error < 5)
    {
+      status = pgmoneta_write_message(http->ssl, http->socket, msg_request);
+      if (status != MESSAGE_STATUS_OK)
+      {
+         error++;
+         pgmoneta_log_debug("Write failed, retrying (%d/5)", error);
+         goto req;
+      }
+   }
+   else
+   {
+      pgmoneta_log_error("Failed to write after 5 attempts");
       goto error;
    }
 
-   res = curl_easy_setopt(handle, CURLOPT_HTTPHEADER, chunk);
+   status = pgmoneta_http_read(http->ssl, http->socket, &response);
 
-   if (res != CURLE_OK)
+   if (response == NULL)
    {
+      pgmoneta_log_error("No response data collected");
       goto error;
    }
 
+   if (http_extract_headers_body(response, http))
+   {
+      pgmoneta_log_error("Failed to extract headers and body");
+      goto error;
+   }
+
+   pgmoneta_log_debug("HTTP Headers: %s", http->headers ? http->headers : "NULL");
+   pgmoneta_log_debug("HTTP Body: %s", http->body ? http->body : "NULL");
+
+   free(request);
+   free(full_request);
+   free(response);
+   free(msg_request);
+   free(user_agent);
+
+   free(http->request_headers);
+   http->request_headers = NULL;
+
    return 0;
 
 error:
+   pgmoneta_log_error("Error in pgmoneta_http_get, cleaning up");
+   if (request)
+   {
+      free(request);
+   }
+   if (full_request)
+   {
+      free(full_request);
+   }
+   if (response)
+   {
+      free(response);
+   }
+   if (msg_request)
+   {
+      free(msg_request);
+   }
+   if (user_agent)
+   {
+      free(user_agent);
+   }
+
+   free(http->request_headers);
+   http->request_headers = NULL;
 
    return 1;
 }
 
 int
-pgmoneta_http_set_request_option(CURL* handle, bool request_type)
+pgmoneta_http_connect(char* hostname, int port, bool secure, struct http** result)
+{
+   pgmoneta_log_debug("Connecting to %s:%d (secure: %d)", hostname, port, secure);
+   struct http* h = NULL;
+   int socket_fd = -1;
+   SSL* ssl = NULL;
+
+   h = (struct http*) malloc(sizeof(struct http));
+   if (h == NULL)
+   {
+      pgmoneta_log_error("Failed to allocate HTTP structure");
+      return 1;
+   }
+
+   memset(h, 0, sizeof(struct http));
+
+   if (pgmoneta_connect(hostname, port, &socket_fd))
+   {
+      pgmoneta_log_error("Failed to connect to %s:%d", hostname, port);
+      free(h);
+      return 1;
+   }
+
+   h->socket = socket_fd;
+
+   if (secure)
+   {
+      SSL_CTX* ctx = NULL;
+
+      if (pgmoneta_create_ssl_ctx(true, &ctx))
+      {
+         pgmoneta_log_error("Failed to create SSL context");
+         pgmoneta_disconnect(socket_fd);
+         free(h);
+         return 1;
+      }
+
+      if (SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION) == 0)
+      {
+         pgmoneta_log_error("Failed to set minimum TLS version");
+         SSL_CTX_free(ctx);
+         pgmoneta_disconnect(socket_fd);
+         free(h);
+         return 1;
+      }
+
+      ssl = SSL_new(ctx);
+      if (ssl == NULL)
+      {
+         pgmoneta_log_error("Failed to create SSL structure");
+         SSL_CTX_free(ctx);
+         pgmoneta_disconnect(socket_fd);
+         free(h);
+         return 1;
+      }
+
+      if (SSL_set_fd(ssl, socket_fd) == 0)
+      {
+         pgmoneta_log_error("Failed to set SSL file descriptor");
+         SSL_free(ssl);
+         SSL_CTX_free(ctx);
+         pgmoneta_disconnect(socket_fd);
+         free(h);
+         return 1;
+      }
+
+      int connect_result;
+      do
+      {
+         connect_result = SSL_connect(ssl);
+
+         if (connect_result != 1)
+         {
+            int err = SSL_get_error(ssl, connect_result);
+            switch (err)
+            {
+               case SSL_ERROR_WANT_READ:
+               case SSL_ERROR_WANT_WRITE:
+                  continue;
+               default:
+                  pgmoneta_log_error("SSL connection failed: %s", ERR_error_string(err, NULL));
+                  SSL_free(ssl);
path: src/libpgmoneta/http.c
user: login: jesperpedersen
body: Same
created_at: 2025-05-11T03:48:47Z
author_association: MEMBER
diff_hunk: @@ -29,99 +29,898 @@
 /* pgmoneta */
 #include <pgmoneta.h>
 #include <http.h>
+#include <logging.h>
+#include <network.h>
+#include <security.h>
 #include <utils.h>
 
-struct curl_slist*
-pgmoneta_http_add_header(struct curl_slist* chunk, char* header, char* value)
+/* system */
+#include <errno.h>
+#include <openssl/err.h>
+#include <unistd.h>
+
+static int http_build_header(int method, char* path, char** request);
+static int http_extract_headers_body(char* response, struct http* http);
+
+int
+pgmoneta_http_add_header(struct http* http, char* name, char* value)
 {
-   char* h = NULL;
+   char* tmp;
 
-   h = pgmoneta_append(h, header);
-   h = pgmoneta_append(h, ": ");
-   h = pgmoneta_append(h, value);
+   tmp = pgmoneta_append(http->request_headers, name);
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   chunk = curl_slist_append(chunk, h);
+   tmp = pgmoneta_append(http->request_headers, ": ");
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
+
+   tmp = pgmoneta_append(http->request_headers, value);
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   free(h);
+   tmp = pgmoneta_append(http->request_headers, "\r\n");
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   return chunk;
+   return 0;
+}
+
+static int
+http_build_header(int method, char* path, char** request)
+{
+   char* r = NULL;
+   *request = NULL;
+
+   if (method == PGMONETA_HTTP_GET)
+   {
+      r = pgmoneta_append(r, "GET ");
+   }
+   else if (method == PGMONETA_HTTP_POST)
+   {
+      r = pgmoneta_append(r, "POST ");
+   }
+   else if (method == PGMONETA_HTTP_PUT)
+   {
+      r = pgmoneta_append(r, "PUT ");
+   }
+   else
+   {
+      pgmoneta_log_error("Invalid HTTP method: %d", method);
+      return 1;
+   }
+
+   r = pgmoneta_append(r, path);
+   r = pgmoneta_append(r, " HTTP/1.1\r\n");
+
+   *request = r;
+
+   return 0;
+}
+
+static int
+http_extract_headers_body(char* response, struct http* http)
+{
+   bool header = true;
+   char* p = NULL;
+   char* response_copy = NULL;
+
+   if (response == NULL)
+   {
+      pgmoneta_log_error("Response is NULL");
+      return 1;
+   }
+
+   response_copy = strdup(response);
+   if (response_copy == NULL)
+   {
+      pgmoneta_log_error("Failed to duplicate response string");
+      return 1;
+   }
+
+   p = strtok(response_copy, "\n");
+   while (p != NULL)
+   {
+      if (*p == '\r')
+      {
+         header = false;
+      }
+      else
+      {
+         if (!pgmoneta_is_number(p, 16))
+         {
+            if (header)
+            {
+               http->headers = pgmoneta_append(http->headers, p);
+               http->headers = pgmoneta_append_char(http->headers, '\n');
+            }
+            else
+            {
+               http->body = pgmoneta_append(http->body, p);
+               http->body = pgmoneta_append_char(http->body, '\n');
+            }
+         }
+      }
+
+      p = strtok(NULL, "\n");
+   }
+
+   free(response_copy);
+   return 0;
 }
 
 int
-pgmoneta_http_set_header_option(CURL* handle, struct curl_slist* chunk)
+pgmoneta_http_get(struct http* http, char* hostname, char* path)
 {
-   CURLcode res;
+   pgmoneta_log_debug("Starting pgmoneta_http_get");
+   struct message* msg_request = NULL;
+   int error = 0;
+   int status;
+   char* request = NULL;
+   char* full_request = NULL;
+   char* response = NULL;
+   char* user_agent = NULL;
+   char* endpoint = path ? path : "/get";
+
+   if (http_build_header(PGMONETA_HTTP_GET, endpoint, &request))
+   {
+      pgmoneta_log_error("Failed to build HTTP header");
+      goto error;
+   }
+
+   pgmoneta_http_add_header(http, "Host", hostname);
+   user_agent = pgmoneta_append(user_agent, "pgmoneta/");
+   user_agent = pgmoneta_append(user_agent, VERSION);
+   pgmoneta_http_add_header(http, "User-Agent", user_agent);
+   pgmoneta_http_add_header(http, "Accept", "text/*");
+   pgmoneta_http_add_header(http, "Connection", "close");
+
+   full_request = pgmoneta_append(NULL, request);
+   full_request = pgmoneta_append(full_request, http->request_headers);
+   full_request = pgmoneta_append(full_request, "\r\n");
+
+   msg_request = (struct message*)malloc(sizeof(struct message));
+   if (msg_request == NULL)
+   {
+      pgmoneta_log_error("Failed to allocate msg_request");
+      goto error;
+   }
+
+   memset(msg_request, 0, sizeof(struct message));
 
-   if (handle == NULL)
+   msg_request->data = full_request;
+   msg_request->length = strlen(full_request) + 1;
+
+   error = 0;
+req:
+   if (error < 5)
    {
+      status = pgmoneta_write_message(http->ssl, http->socket, msg_request);
+      if (status != MESSAGE_STATUS_OK)
+      {
+         error++;
+         pgmoneta_log_debug("Write failed, retrying (%d/5)", error);
+         goto req;
+      }
+   }
+   else
+   {
+      pgmoneta_log_error("Failed to write after 5 attempts");
       goto error;
    }
 
-   res = curl_easy_setopt(handle, CURLOPT_HTTPHEADER, chunk);
+   status = pgmoneta_http_read(http->ssl, http->socket, &response);
 
-   if (res != CURLE_OK)
+   if (response == NULL)
    {
+      pgmoneta_log_error("No response data collected");
       goto error;
    }
 
+   if (http_extract_headers_body(response, http))
+   {
+      pgmoneta_log_error("Failed to extract headers and body");
+      goto error;
+   }
+
+   pgmoneta_log_debug("HTTP Headers: %s", http->headers ? http->headers : "NULL");
+   pgmoneta_log_debug("HTTP Body: %s", http->body ? http->body : "NULL");
+
+   free(request);
+   free(full_request);
+   free(response);
+   free(msg_request);
+   free(user_agent);
+
+   free(http->request_headers);
+   http->request_headers = NULL;
+
    return 0;
 
 error:
+   pgmoneta_log_error("Error in pgmoneta_http_get, cleaning up");
+   if (request)
+   {
+      free(request);
+   }
+   if (full_request)
+   {
+      free(full_request);
+   }
+   if (response)
+   {
+      free(response);
+   }
+   if (msg_request)
+   {
+      free(msg_request);
+   }
+   if (user_agent)
+   {
+      free(user_agent);
+   }
+
+   free(http->request_headers);
+   http->request_headers = NULL;
 
    return 1;
 }
 
 int
-pgmoneta_http_set_request_option(CURL* handle, bool request_type)
+pgmoneta_http_connect(char* hostname, int port, bool secure, struct http** result)
+{
+   pgmoneta_log_debug("Connecting to %s:%d (secure: %d)", hostname, port, secure);
+   struct http* h = NULL;
+   int socket_fd = -1;
+   SSL* ssl = NULL;
+
+   h = (struct http*) malloc(sizeof(struct http));
+   if (h == NULL)
+   {
+      pgmoneta_log_error("Failed to allocate HTTP structure");
+      return 1;
+   }
+
+   memset(h, 0, sizeof(struct http));
+
+   if (pgmoneta_connect(hostname, port, &socket_fd))
+   {
+      pgmoneta_log_error("Failed to connect to %s:%d", hostname, port);
+      free(h);
+      return 1;
+   }
+
+   h->socket = socket_fd;
+
+   if (secure)
+   {
+      SSL_CTX* ctx = NULL;
+
+      if (pgmoneta_create_ssl_ctx(true, &ctx))
+      {
+         pgmoneta_log_error("Failed to create SSL context");
+         pgmoneta_disconnect(socket_fd);
+         free(h);
+         return 1;
+      }
+
+      if (SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION) == 0)
+      {
+         pgmoneta_log_error("Failed to set minimum TLS version");
+         SSL_CTX_free(ctx);
+         pgmoneta_disconnect(socket_fd);
+         free(h);
+         return 1;
+      }
+
+      ssl = SSL_new(ctx);
+      if (ssl == NULL)
+      {
+         pgmoneta_log_error("Failed to create SSL structure");
+         SSL_CTX_free(ctx);
+         pgmoneta_disconnect(socket_fd);
+         free(h);
+         return 1;
+      }
+
+      if (SSL_set_fd(ssl, socket_fd) == 0)
+      {
+         pgmoneta_log_error("Failed to set SSL file descriptor");
+         SSL_free(ssl);
+         SSL_CTX_free(ctx);
+         pgmoneta_disconnect(socket_fd);
+         free(h);
+         return 1;
+      }
+
+      int connect_result;
+      do
+      {
+         connect_result = SSL_connect(ssl);
+
+         if (connect_result != 1)
+         {
+            int err = SSL_get_error(ssl, connect_result);
+            switch (err)
+            {
+               case SSL_ERROR_WANT_READ:
+               case SSL_ERROR_WANT_WRITE:
+                  continue;
+               default:
+                  pgmoneta_log_error("SSL connection failed: %s", ERR_error_string(err, NULL));
+                  SSL_free(ssl);
+                  SSL_CTX_free(ctx);
+                  pgmoneta_disconnect(socket_fd);
+                  free(h);
+                  return 1;
+            }
+         }
+      }
+      while (connect_result != 1);
+
+      h->ssl = ssl;
+   }
+
+   *result = h;
+
+   return 0;
+}
+
+int
+pgmoneta_http_post(struct http* http, char* hostname, char* path, char* data, size_t length)
 {
-   CURLcode res = -1;
+   pgmoneta_log_debug("Starting pgmoneta_http_post");
+   struct message* msg_request = NULL;
+   int error = 0;
+   int status;
+   char* request = NULL;
+   char* full_request = NULL;
+   char* response = NULL;
+   char* user_agent = NULL;
+   char content_length[32];
+
+   if (http_build_header(PGMONETA_HTTP_POST, path, &request))
+   {
+      pgmoneta_log_error("Failed to build HTTP header");
+      goto error;
+   }
+
+   pgmoneta_http_add_header(http, "Host", hostname);
+   user_agent = pgmoneta_append(user_agent, "pgmoneta/");
+   user_agent = pgmoneta_append(user_agent, VERSION);
+   pgmoneta_http_add_header(http, "User-Agent", user_agent);
+   pgmoneta_http_add_header(http, "Connection", "close");
+
+   sprintf(content_length, "%zu", length);
+   pgmoneta_http_add_header(http, "Content-Length", content_length);
+   pgmoneta_http_add_header(http, "Content-Type", "application/x-www-form-urlencoded");
+
+   full_request = pgmoneta_append(NULL, request);
+   full_request = pgmoneta_append(full_request, http->request_headers);
+   full_request = pgmoneta_append(full_request, "\r\n");
 
-   if (handle == NULL)
+   if (data && length > 0)
    {
+      full_request = pgmoneta_append(full_request, data);
+   }
+
+   msg_request = (struct message*)malloc(sizeof(struct message));
+   if (msg_request == NULL)
+   {
+      pgmoneta_log_error("Failed to allocate msg_request");
       goto error;
    }
 
-   if (request_type == HTTP_GET)
+   memset(msg_request, 0, sizeof(struct message));
+
+   msg_request->data = full_request;
+   msg_request->length = strlen(full_request) + 1;
+
+   error = 0;
+req:
+   if (error < 5)
+   {
+      status = pgmoneta_write_message(http->ssl, http->socket, msg_request);
+      if (status != MESSAGE_STATUS_OK)
+      {
+         error++;
+         pgmoneta_log_debug("Write failed, retrying (%d/5)", error);
+         goto req;
+      }
+   }
+   else
    {
-      res = curl_easy_setopt(handle, CURLOPT_HTTPGET, 1L);
+      pgmoneta_log_error("Failed to write after 5 attempts");
+      goto error;
    }
-   else if (request_type == HTTP_PUT)
+
+   status = pgmoneta_http_read(http->ssl, http->socket, &response);
+
+   if (response == NULL)
    {
-      res = curl_easy_setopt(handle, CURLOPT_UPLOAD, 1L);
+      pgmoneta_log_error("No response data collected");
+      goto error;
    }
 
-   if (res != CURLE_OK)
+   if (http_extract_headers_body(response, http))
    {
+      pgmoneta_log_error("Failed to extract headers and body");
       goto error;
    }
 
+   free(request);
+   free(full_request);
+   free(response);
+   free(msg_request);
+   free(user_agent);
+
+   free(http->request_headers);
+   http->request_headers = NULL;
+
    return 0;
 
 error:
+   pgmoneta_log_error("Error in pgmoneta_http_post, cleaning up");
+   if (request)
path: src/libpgmoneta/http.c
user: login: jesperpedersen
body: No guards
created_at: 2025-05-11T03:49:19Z
author_association: MEMBER
diff_hunk: @@ -29,99 +29,898 @@
 /* pgmoneta */
 #include <pgmoneta.h>
 #include <http.h>
+#include <logging.h>
+#include <network.h>
+#include <security.h>
 #include <utils.h>
 
-struct curl_slist*
-pgmoneta_http_add_header(struct curl_slist* chunk, char* header, char* value)
+/* system */
+#include <errno.h>
+#include <openssl/err.h>
+#include <unistd.h>
+
+static int http_build_header(int method, char* path, char** request);
+static int http_extract_headers_body(char* response, struct http* http);
+
+int
+pgmoneta_http_add_header(struct http* http, char* name, char* value)
 {
-   char* h = NULL;
+   char* tmp;
 
-   h = pgmoneta_append(h, header);
-   h = pgmoneta_append(h, ": ");
-   h = pgmoneta_append(h, value);
+   tmp = pgmoneta_append(http->request_headers, name);
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   chunk = curl_slist_append(chunk, h);
+   tmp = pgmoneta_append(http->request_headers, ": ");
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
+
+   tmp = pgmoneta_append(http->request_headers, value);
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   free(h);
+   tmp = pgmoneta_append(http->request_headers, "\r\n");
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   return chunk;
+   return 0;
+}
+
+static int
+http_build_header(int method, char* path, char** request)
+{
+   char* r = NULL;
+   *request = NULL;
+
+   if (method == PGMONETA_HTTP_GET)
+   {
+      r = pgmoneta_append(r, "GET ");
+   }
+   else if (method == PGMONETA_HTTP_POST)
+   {
+      r = pgmoneta_append(r, "POST ");
+   }
+   else if (method == PGMONETA_HTTP_PUT)
+   {
+      r = pgmoneta_append(r, "PUT ");
+   }
+   else
+   {
+      pgmoneta_log_error("Invalid HTTP method: %d", method);
+      return 1;
+   }
+
+   r = pgmoneta_append(r, path);
+   r = pgmoneta_append(r, " HTTP/1.1\r\n");
+
+   *request = r;
+
+   return 0;
+}
+
+static int
+http_extract_headers_body(char* response, struct http* http)
+{
+   bool header = true;
+   char* p = NULL;
+   char* response_copy = NULL;
+
+   if (response == NULL)
+   {
+      pgmoneta_log_error("Response is NULL");
+      return 1;
+   }
+
+   response_copy = strdup(response);
+   if (response_copy == NULL)
+   {
+      pgmoneta_log_error("Failed to duplicate response string");
+      return 1;
+   }
+
+   p = strtok(response_copy, "\n");
+   while (p != NULL)
+   {
+      if (*p == '\r')
+      {
+         header = false;
+      }
+      else
+      {
+         if (!pgmoneta_is_number(p, 16))
+         {
+            if (header)
+            {
+               http->headers = pgmoneta_append(http->headers, p);
+               http->headers = pgmoneta_append_char(http->headers, '\n');
+            }
+            else
+            {
+               http->body = pgmoneta_append(http->body, p);
+               http->body = pgmoneta_append_char(http->body, '\n');
+            }
+         }
+      }
+
+      p = strtok(NULL, "\n");
+   }
+
+   free(response_copy);
+   return 0;
 }
 
 int
-pgmoneta_http_set_header_option(CURL* handle, struct curl_slist* chunk)
+pgmoneta_http_get(struct http* http, char* hostname, char* path)
 {
-   CURLcode res;
+   pgmoneta_log_debug("Starting pgmoneta_http_get");
+   struct message* msg_request = NULL;
+   int error = 0;
+   int status;
+   char* request = NULL;
+   char* full_request = NULL;
+   char* response = NULL;
+   char* user_agent = NULL;
+   char* endpoint = path ? path : "/get";
+
+   if (http_build_header(PGMONETA_HTTP_GET, endpoint, &request))
+   {
+      pgmoneta_log_error("Failed to build HTTP header");
+      goto error;
+   }
+
+   pgmoneta_http_add_header(http, "Host", hostname);
+   user_agent = pgmoneta_append(user_agent, "pgmoneta/");
+   user_agent = pgmoneta_append(user_agent, VERSION);
+   pgmoneta_http_add_header(http, "User-Agent", user_agent);
+   pgmoneta_http_add_header(http, "Accept", "text/*");
+   pgmoneta_http_add_header(http, "Connection", "close");
+
+   full_request = pgmoneta_append(NULL, request);
+   full_request = pgmoneta_append(full_request, http->request_headers);
+   full_request = pgmoneta_append(full_request, "\r\n");
+
+   msg_request = (struct message*)malloc(sizeof(struct message));
+   if (msg_request == NULL)
+   {
+      pgmoneta_log_error("Failed to allocate msg_request");
+      goto error;
+   }
+
+   memset(msg_request, 0, sizeof(struct message));
 
-   if (handle == NULL)
+   msg_request->data = full_request;
+   msg_request->length = strlen(full_request) + 1;
+
+   error = 0;
+req:
+   if (error < 5)
    {
+      status = pgmoneta_write_message(http->ssl, http->socket, msg_request);
+      if (status != MESSAGE_STATUS_OK)
+      {
+         error++;
+         pgmoneta_log_debug("Write failed, retrying (%d/5)", error);
+         goto req;
+      }
+   }
+   else
+   {
+      pgmoneta_log_error("Failed to write after 5 attempts");
       goto error;
    }
 
-   res = curl_easy_setopt(handle, CURLOPT_HTTPHEADER, chunk);
+   status = pgmoneta_http_read(http->ssl, http->socket, &response);
 
-   if (res != CURLE_OK)
+   if (response == NULL)
    {
+      pgmoneta_log_error("No response data collected");
       goto error;
    }
 
+   if (http_extract_headers_body(response, http))
+   {
+      pgmoneta_log_error("Failed to extract headers and body");
+      goto error;
+   }
+
+   pgmoneta_log_debug("HTTP Headers: %s", http->headers ? http->headers : "NULL");
+   pgmoneta_log_debug("HTTP Body: %s", http->body ? http->body : "NULL");
+
+   free(request);
+   free(full_request);
+   free(response);
+   free(msg_request);
+   free(user_agent);
+
+   free(http->request_headers);
+   http->request_headers = NULL;
+
    return 0;
 
 error:
+   pgmoneta_log_error("Error in pgmoneta_http_get, cleaning up");
+   if (request)
+   {
+      free(request);
+   }
+   if (full_request)
+   {
+      free(full_request);
+   }
+   if (response)
+   {
+      free(response);
+   }
+   if (msg_request)
+   {
+      free(msg_request);
+   }
+   if (user_agent)
+   {
+      free(user_agent);
+   }
+
+   free(http->request_headers);
+   http->request_headers = NULL;
 
    return 1;
 }
 
 int
-pgmoneta_http_set_request_option(CURL* handle, bool request_type)
+pgmoneta_http_connect(char* hostname, int port, bool secure, struct http** result)
+{
+   pgmoneta_log_debug("Connecting to %s:%d (secure: %d)", hostname, port, secure);
+   struct http* h = NULL;
+   int socket_fd = -1;
+   SSL* ssl = NULL;
+
+   h = (struct http*) malloc(sizeof(struct http));
+   if (h == NULL)
+   {
+      pgmoneta_log_error("Failed to allocate HTTP structure");
+      return 1;
+   }
+
+   memset(h, 0, sizeof(struct http));
+
+   if (pgmoneta_connect(hostname, port, &socket_fd))
+   {
+      pgmoneta_log_error("Failed to connect to %s:%d", hostname, port);
+      free(h);
+      return 1;
+   }
+
+   h->socket = socket_fd;
+
+   if (secure)
+   {
+      SSL_CTX* ctx = NULL;
+
+      if (pgmoneta_create_ssl_ctx(true, &ctx))
+      {
+         pgmoneta_log_error("Failed to create SSL context");
+         pgmoneta_disconnect(socket_fd);
+         free(h);
+         return 1;
+      }
+
+      if (SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION) == 0)
+      {
+         pgmoneta_log_error("Failed to set minimum TLS version");
+         SSL_CTX_free(ctx);
+         pgmoneta_disconnect(socket_fd);
+         free(h);
+         return 1;
+      }
+
+      ssl = SSL_new(ctx);
+      if (ssl == NULL)
+      {
+         pgmoneta_log_error("Failed to create SSL structure");
+         SSL_CTX_free(ctx);
+         pgmoneta_disconnect(socket_fd);
+         free(h);
+         return 1;
+      }
+
+      if (SSL_set_fd(ssl, socket_fd) == 0)
+      {
+         pgmoneta_log_error("Failed to set SSL file descriptor");
+         SSL_free(ssl);
+         SSL_CTX_free(ctx);
+         pgmoneta_disconnect(socket_fd);
+         free(h);
+         return 1;
+      }
+
+      int connect_result;
+      do
+      {
+         connect_result = SSL_connect(ssl);
+
+         if (connect_result != 1)
+         {
+            int err = SSL_get_error(ssl, connect_result);
+            switch (err)
+            {
+               case SSL_ERROR_WANT_READ:
+               case SSL_ERROR_WANT_WRITE:
+                  continue;
+               default:
+                  pgmoneta_log_error("SSL connection failed: %s", ERR_error_string(err, NULL));
+                  SSL_free(ssl);
+                  SSL_CTX_free(ctx);
+                  pgmoneta_disconnect(socket_fd);
+                  free(h);
+                  return 1;
+            }
+         }
+      }
+      while (connect_result != 1);
+
+      h->ssl = ssl;
+   }
+
+   *result = h;
+
+   return 0;
+}
+
+int
+pgmoneta_http_post(struct http* http, char* hostname, char* path, char* data, size_t length)
 {
-   CURLcode res = -1;
+   pgmoneta_log_debug("Starting pgmoneta_http_post");
+   struct message* msg_request = NULL;
+   int error = 0;
+   int status;
+   char* request = NULL;
+   char* full_request = NULL;
+   char* response = NULL;
+   char* user_agent = NULL;
+   char content_length[32];
+
+   if (http_build_header(PGMONETA_HTTP_POST, path, &request))
+   {
+      pgmoneta_log_error("Failed to build HTTP header");
+      goto error;
+   }
+
+   pgmoneta_http_add_header(http, "Host", hostname);
+   user_agent = pgmoneta_append(user_agent, "pgmoneta/");
+   user_agent = pgmoneta_append(user_agent, VERSION);
+   pgmoneta_http_add_header(http, "User-Agent", user_agent);
+   pgmoneta_http_add_header(http, "Connection", "close");
+
+   sprintf(content_length, "%zu", length);
+   pgmoneta_http_add_header(http, "Content-Length", content_length);
+   pgmoneta_http_add_header(http, "Content-Type", "application/x-www-form-urlencoded");
+
+   full_request = pgmoneta_append(NULL, request);
+   full_request = pgmoneta_append(full_request, http->request_headers);
+   full_request = pgmoneta_append(full_request, "\r\n");
 
-   if (handle == NULL)
+   if (data && length > 0)
    {
+      full_request = pgmoneta_append(full_request, data);
+   }
+
+   msg_request = (struct message*)malloc(sizeof(struct message));
+   if (msg_request == NULL)
+   {
+      pgmoneta_log_error("Failed to allocate msg_request");
       goto error;
    }
 
-   if (request_type == HTTP_GET)
+   memset(msg_request, 0, sizeof(struct message));
+
+   msg_request->data = full_request;
+   msg_request->length = strlen(full_request) + 1;
+
+   error = 0;
+req:
+   if (error < 5)
+   {
+      status = pgmoneta_write_message(http->ssl, http->socket, msg_request);
+      if (status != MESSAGE_STATUS_OK)
+      {
+         error++;
+         pgmoneta_log_debug("Write failed, retrying (%d/5)", error);
+         goto req;
+      }
+   }
+   else
    {
-      res = curl_easy_setopt(handle, CURLOPT_HTTPGET, 1L);
+      pgmoneta_log_error("Failed to write after 5 attempts");
+      goto error;
    }
-   else if (request_type == HTTP_PUT)
+
+   status = pgmoneta_http_read(http->ssl, http->socket, &response);
+
+   if (response == NULL)
    {
-      res = curl_easy_setopt(handle, CURLOPT_UPLOAD, 1L);
+      pgmoneta_log_error("No response data collected");
+      goto error;
    }
 
-   if (res != CURLE_OK)
+   if (http_extract_headers_body(response, http))
    {
+      pgmoneta_log_error("Failed to extract headers and body");
       goto error;
    }
 
+   free(request);
+   free(full_request);
+   free(response);
+   free(msg_request);
+   free(user_agent);
+
+   free(http->request_headers);
+   http->request_headers = NULL;
+
    return 0;
 
 error:
+   pgmoneta_log_error("Error in pgmoneta_http_post, cleaning up");
+   if (request)
+   {
+      free(request);
+   }
+   if (full_request)
+   {
+      free(full_request);
+   }
+   if (response)
+   {
+      free(response);
+   }
+   if (msg_request)
+   {
+      free(msg_request);
+   }
+   if (user_agent)
+   {
+      free(user_agent);
+   }
+
+   free(http->request_headers);
+   http->request_headers = NULL;
 
    return 1;
 }
 
 int
-pgmoneta_http_set_url_option(CURL* handle, char* url)
+pgmoneta_http_put(struct http* http, char* hostname, char* path, const void* data, size_t length)
 {
-   CURLcode res;
+   pgmoneta_log_debug("Starting pgmoneta_http_put");
path: src/libpgmoneta/http.c
user: login: jesperpedersen
body: Move
created_at: 2025-05-11T03:49:27Z
author_association: MEMBER
diff_hunk: @@ -29,99 +29,898 @@
 /* pgmoneta */
 #include <pgmoneta.h>
 #include <http.h>
+#include <logging.h>
+#include <network.h>
+#include <security.h>
 #include <utils.h>
 
-struct curl_slist*
-pgmoneta_http_add_header(struct curl_slist* chunk, char* header, char* value)
+/* system */
+#include <errno.h>
+#include <openssl/err.h>
+#include <unistd.h>
+
+static int http_build_header(int method, char* path, char** request);
+static int http_extract_headers_body(char* response, struct http* http);
+
+int
+pgmoneta_http_add_header(struct http* http, char* name, char* value)
 {
-   char* h = NULL;
+   char* tmp;
 
-   h = pgmoneta_append(h, header);
-   h = pgmoneta_append(h, ": ");
-   h = pgmoneta_append(h, value);
+   tmp = pgmoneta_append(http->request_headers, name);
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   chunk = curl_slist_append(chunk, h);
+   tmp = pgmoneta_append(http->request_headers, ": ");
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
+
+   tmp = pgmoneta_append(http->request_headers, value);
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   free(h);
+   tmp = pgmoneta_append(http->request_headers, "\r\n");
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   return chunk;
+   return 0;
+}
+
+static int
+http_build_header(int method, char* path, char** request)
+{
+   char* r = NULL;
+   *request = NULL;
+
+   if (method == PGMONETA_HTTP_GET)
+   {
+      r = pgmoneta_append(r, "GET ");
+   }
+   else if (method == PGMONETA_HTTP_POST)
+   {
+      r = pgmoneta_append(r, "POST ");
+   }
+   else if (method == PGMONETA_HTTP_PUT)
+   {
+      r = pgmoneta_append(r, "PUT ");
+   }
+   else
+   {
+      pgmoneta_log_error("Invalid HTTP method: %d", method);
+      return 1;
+   }
+
+   r = pgmoneta_append(r, path);
+   r = pgmoneta_append(r, " HTTP/1.1\r\n");
+
+   *request = r;
+
+   return 0;
+}
+
+static int
+http_extract_headers_body(char* response, struct http* http)
+{
+   bool header = true;
+   char* p = NULL;
+   char* response_copy = NULL;
+
+   if (response == NULL)
+   {
+      pgmoneta_log_error("Response is NULL");
+      return 1;
+   }
+
+   response_copy = strdup(response);
+   if (response_copy == NULL)
+   {
+      pgmoneta_log_error("Failed to duplicate response string");
+      return 1;
+   }
+
+   p = strtok(response_copy, "\n");
+   while (p != NULL)
+   {
+      if (*p == '\r')
+      {
+         header = false;
+      }
+      else
+      {
+         if (!pgmoneta_is_number(p, 16))
+         {
+            if (header)
+            {
+               http->headers = pgmoneta_append(http->headers, p);
+               http->headers = pgmoneta_append_char(http->headers, '\n');
+            }
+            else
+            {
+               http->body = pgmoneta_append(http->body, p);
+               http->body = pgmoneta_append_char(http->body, '\n');
+            }
+         }
+      }
+
+      p = strtok(NULL, "\n");
+   }
+
+   free(response_copy);
+   return 0;
 }
 
 int
-pgmoneta_http_set_header_option(CURL* handle, struct curl_slist* chunk)
+pgmoneta_http_get(struct http* http, char* hostname, char* path)
 {
-   CURLcode res;
+   pgmoneta_log_debug("Starting pgmoneta_http_get");
+   struct message* msg_request = NULL;
+   int error = 0;
+   int status;
+   char* request = NULL;
+   char* full_request = NULL;
+   char* response = NULL;
+   char* user_agent = NULL;
+   char* endpoint = path ? path : "/get";
+
+   if (http_build_header(PGMONETA_HTTP_GET, endpoint, &request))
+   {
+      pgmoneta_log_error("Failed to build HTTP header");
+      goto error;
+   }
+
+   pgmoneta_http_add_header(http, "Host", hostname);
+   user_agent = pgmoneta_append(user_agent, "pgmoneta/");
+   user_agent = pgmoneta_append(user_agent, VERSION);
+   pgmoneta_http_add_header(http, "User-Agent", user_agent);
+   pgmoneta_http_add_header(http, "Accept", "text/*");
+   pgmoneta_http_add_header(http, "Connection", "close");
+
+   full_request = pgmoneta_append(NULL, request);
+   full_request = pgmoneta_append(full_request, http->request_headers);
+   full_request = pgmoneta_append(full_request, "\r\n");
+
+   msg_request = (struct message*)malloc(sizeof(struct message));
+   if (msg_request == NULL)
+   {
+      pgmoneta_log_error("Failed to allocate msg_request");
+      goto error;
+   }
+
+   memset(msg_request, 0, sizeof(struct message));
 
-   if (handle == NULL)
+   msg_request->data = full_request;
+   msg_request->length = strlen(full_request) + 1;
+
+   error = 0;
+req:
+   if (error < 5)
    {
+      status = pgmoneta_write_message(http->ssl, http->socket, msg_request);
+      if (status != MESSAGE_STATUS_OK)
+      {
+         error++;
+         pgmoneta_log_debug("Write failed, retrying (%d/5)", error);
+         goto req;
+      }
+   }
+   else
+   {
+      pgmoneta_log_error("Failed to write after 5 attempts");
       goto error;
    }
 
-   res = curl_easy_setopt(handle, CURLOPT_HTTPHEADER, chunk);
+   status = pgmoneta_http_read(http->ssl, http->socket, &response);
 
-   if (res != CURLE_OK)
+   if (response == NULL)
    {
+      pgmoneta_log_error("No response data collected");
       goto error;
    }
 
+   if (http_extract_headers_body(response, http))
+   {
+      pgmoneta_log_error("Failed to extract headers and body");
+      goto error;
+   }
+
+   pgmoneta_log_debug("HTTP Headers: %s", http->headers ? http->headers : "NULL");
+   pgmoneta_log_debug("HTTP Body: %s", http->body ? http->body : "NULL");
+
+   free(request);
+   free(full_request);
+   free(response);
+   free(msg_request);
+   free(user_agent);
+
+   free(http->request_headers);
+   http->request_headers = NULL;
+
    return 0;
 
 error:
+   pgmoneta_log_error("Error in pgmoneta_http_get, cleaning up");
+   if (request)
+   {
+      free(request);
+   }
+   if (full_request)
+   {
+      free(full_request);
+   }
+   if (response)
+   {
+      free(response);
+   }
+   if (msg_request)
+   {
+      free(msg_request);
+   }
+   if (user_agent)
+   {
+      free(user_agent);
+   }
+
+   free(http->request_headers);
+   http->request_headers = NULL;
 
    return 1;
 }
 
 int
-pgmoneta_http_set_request_option(CURL* handle, bool request_type)
+pgmoneta_http_connect(char* hostname, int port, bool secure, struct http** result)
+{
+   pgmoneta_log_debug("Connecting to %s:%d (secure: %d)", hostname, port, secure);
+   struct http* h = NULL;
+   int socket_fd = -1;
+   SSL* ssl = NULL;
+
+   h = (struct http*) malloc(sizeof(struct http));
+   if (h == NULL)
+   {
+      pgmoneta_log_error("Failed to allocate HTTP structure");
+      return 1;
+   }
+
+   memset(h, 0, sizeof(struct http));
+
+   if (pgmoneta_connect(hostname, port, &socket_fd))
+   {
+      pgmoneta_log_error("Failed to connect to %s:%d", hostname, port);
+      free(h);
+      return 1;
+   }
+
+   h->socket = socket_fd;
+
+   if (secure)
+   {
+      SSL_CTX* ctx = NULL;
+
+      if (pgmoneta_create_ssl_ctx(true, &ctx))
+      {
+         pgmoneta_log_error("Failed to create SSL context");
+         pgmoneta_disconnect(socket_fd);
+         free(h);
+         return 1;
+      }
+
+      if (SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION) == 0)
+      {
+         pgmoneta_log_error("Failed to set minimum TLS version");
+         SSL_CTX_free(ctx);
+         pgmoneta_disconnect(socket_fd);
+         free(h);
+         return 1;
+      }
+
+      ssl = SSL_new(ctx);
+      if (ssl == NULL)
+      {
+         pgmoneta_log_error("Failed to create SSL structure");
+         SSL_CTX_free(ctx);
+         pgmoneta_disconnect(socket_fd);
+         free(h);
+         return 1;
+      }
+
+      if (SSL_set_fd(ssl, socket_fd) == 0)
+      {
+         pgmoneta_log_error("Failed to set SSL file descriptor");
+         SSL_free(ssl);
+         SSL_CTX_free(ctx);
+         pgmoneta_disconnect(socket_fd);
+         free(h);
+         return 1;
+      }
+
+      int connect_result;
+      do
+      {
+         connect_result = SSL_connect(ssl);
+
+         if (connect_result != 1)
+         {
+            int err = SSL_get_error(ssl, connect_result);
+            switch (err)
+            {
+               case SSL_ERROR_WANT_READ:
+               case SSL_ERROR_WANT_WRITE:
+                  continue;
+               default:
+                  pgmoneta_log_error("SSL connection failed: %s", ERR_error_string(err, NULL));
+                  SSL_free(ssl);
+                  SSL_CTX_free(ctx);
+                  pgmoneta_disconnect(socket_fd);
+                  free(h);
+                  return 1;
+            }
+         }
+      }
+      while (connect_result != 1);
+
+      h->ssl = ssl;
+   }
+
+   *result = h;
+
+   return 0;
+}
+
+int
+pgmoneta_http_post(struct http* http, char* hostname, char* path, char* data, size_t length)
 {
-   CURLcode res = -1;
+   pgmoneta_log_debug("Starting pgmoneta_http_post");
+   struct message* msg_request = NULL;
+   int error = 0;
+   int status;
+   char* request = NULL;
+   char* full_request = NULL;
+   char* response = NULL;
+   char* user_agent = NULL;
+   char content_length[32];
+
+   if (http_build_header(PGMONETA_HTTP_POST, path, &request))
+   {
+      pgmoneta_log_error("Failed to build HTTP header");
+      goto error;
+   }
+
+   pgmoneta_http_add_header(http, "Host", hostname);
+   user_agent = pgmoneta_append(user_agent, "pgmoneta/");
+   user_agent = pgmoneta_append(user_agent, VERSION);
+   pgmoneta_http_add_header(http, "User-Agent", user_agent);
+   pgmoneta_http_add_header(http, "Connection", "close");
+
+   sprintf(content_length, "%zu", length);
+   pgmoneta_http_add_header(http, "Content-Length", content_length);
+   pgmoneta_http_add_header(http, "Content-Type", "application/x-www-form-urlencoded");
+
+   full_request = pgmoneta_append(NULL, request);
+   full_request = pgmoneta_append(full_request, http->request_headers);
+   full_request = pgmoneta_append(full_request, "\r\n");
 
-   if (handle == NULL)
+   if (data && length > 0)
    {
+      full_request = pgmoneta_append(full_request, data);
+   }
+
+   msg_request = (struct message*)malloc(sizeof(struct message));
+   if (msg_request == NULL)
+   {
+      pgmoneta_log_error("Failed to allocate msg_request");
       goto error;
    }
 
-   if (request_type == HTTP_GET)
+   memset(msg_request, 0, sizeof(struct message));
+
+   msg_request->data = full_request;
+   msg_request->length = strlen(full_request) + 1;
+
+   error = 0;
+req:
+   if (error < 5)
+   {
+      status = pgmoneta_write_message(http->ssl, http->socket, msg_request);
+      if (status != MESSAGE_STATUS_OK)
+      {
+         error++;
+         pgmoneta_log_debug("Write failed, retrying (%d/5)", error);
+         goto req;
+      }
+   }
+   else
    {
-      res = curl_easy_setopt(handle, CURLOPT_HTTPGET, 1L);
+      pgmoneta_log_error("Failed to write after 5 attempts");
+      goto error;
    }
-   else if (request_type == HTTP_PUT)
+
+   status = pgmoneta_http_read(http->ssl, http->socket, &response);
+
+   if (response == NULL)
    {
-      res = curl_easy_setopt(handle, CURLOPT_UPLOAD, 1L);
+      pgmoneta_log_error("No response data collected");
+      goto error;
    }
 
-   if (res != CURLE_OK)
+   if (http_extract_headers_body(response, http))
    {
+      pgmoneta_log_error("Failed to extract headers and body");
       goto error;
    }
 
+   free(request);
+   free(full_request);
+   free(response);
+   free(msg_request);
+   free(user_agent);
+
+   free(http->request_headers);
+   http->request_headers = NULL;
+
    return 0;
 
 error:
+   pgmoneta_log_error("Error in pgmoneta_http_post, cleaning up");
+   if (request)
+   {
+      free(request);
+   }
+   if (full_request)
+   {
+      free(full_request);
+   }
+   if (response)
+   {
+      free(response);
+   }
+   if (msg_request)
+   {
+      free(msg_request);
+   }
+   if (user_agent)
+   {
+      free(user_agent);
+   }
+
+   free(http->request_headers);
+   http->request_headers = NULL;
 
    return 1;
 }
 
 int
-pgmoneta_http_set_url_option(CURL* handle, char* url)
+pgmoneta_http_put(struct http* http, char* hostname, char* path, const void* data, size_t length)
 {
-   CURLcode res;
+   pgmoneta_log_debug("Starting pgmoneta_http_put");
+   struct message* msg_request = NULL;
+   int error = 0;
+   int status;
+   char* request = NULL;
+   char* full_request = NULL;
+   char* response = NULL;
+   char* user_agent = NULL;
+   char content_length[32];
+
+   if (http_build_header(PGMONETA_HTTP_PUT, path, &request))
+   {
+      pgmoneta_log_error("Failed to build HTTP header");
+      goto error;
+   }
+
+   pgmoneta_http_add_header(http, "Host", hostname);
+   user_agent = pgmoneta_append(user_agent, "pgmoneta/");
+   user_agent = pgmoneta_append(user_agent, VERSION);
+   pgmoneta_http_add_header(http, "User-Agent", user_agent);
+   pgmoneta_http_add_header(http, "Connection", "close");
 
-   if (handle == NULL)
+   sprintf(content_length, "%zu", length);
+   pgmoneta_http_add_header(http, "Content-Length", content_length);
+   pgmoneta_http_add_header(http, "Content-Type", "application/octet-stream");
+
+   full_request = pgmoneta_append(NULL, request);
+   full_request = pgmoneta_append(full_request, http->request_headers);
+   full_request = pgmoneta_append(full_request, "\r\n");
+
+   size_t headers_len = strlen(full_request);
+   size_t total_len = headers_len + length;
+
+   char* complete_request = malloc(total_len + 1);
+   if (complete_request == NULL)
+   {
+      pgmoneta_log_error("Failed to allocate complete request");
+      goto error;
+   }
+
+   memcpy(complete_request, full_request, headers_len);
+
+   if (data && length > 0)
+   {
+      memcpy(complete_request + headers_len, data, length);
+   }
+
+   complete_request[total_len] = '\0';
+
+   msg_request = (struct message*)malloc(sizeof(struct message));
+   if (msg_request == NULL)
    {
+      pgmoneta_log_error("Failed to allocate msg_request");
+      free(complete_request);
path: src/libpgmoneta/http.c
user: login: jesperpedersen
body: Move
created_at: 2025-05-11T03:50:03Z
author_association: MEMBER
diff_hunk: @@ -29,99 +29,898 @@
 /* pgmoneta */
 #include <pgmoneta.h>
 #include <http.h>
+#include <logging.h>
+#include <network.h>
+#include <security.h>
 #include <utils.h>
 
-struct curl_slist*
-pgmoneta_http_add_header(struct curl_slist* chunk, char* header, char* value)
+/* system */
+#include <errno.h>
+#include <openssl/err.h>
+#include <unistd.h>
+
+static int http_build_header(int method, char* path, char** request);
+static int http_extract_headers_body(char* response, struct http* http);
+
+int
+pgmoneta_http_add_header(struct http* http, char* name, char* value)
 {
-   char* h = NULL;
+   char* tmp;
 
-   h = pgmoneta_append(h, header);
-   h = pgmoneta_append(h, ": ");
-   h = pgmoneta_append(h, value);
+   tmp = pgmoneta_append(http->request_headers, name);
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   chunk = curl_slist_append(chunk, h);
+   tmp = pgmoneta_append(http->request_headers, ": ");
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
+
+   tmp = pgmoneta_append(http->request_headers, value);
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   free(h);
+   tmp = pgmoneta_append(http->request_headers, "\r\n");
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   return chunk;
+   return 0;
+}
+
+static int
+http_build_header(int method, char* path, char** request)
+{
+   char* r = NULL;
+   *request = NULL;
+
+   if (method == PGMONETA_HTTP_GET)
+   {
+      r = pgmoneta_append(r, "GET ");
+   }
+   else if (method == PGMONETA_HTTP_POST)
+   {
+      r = pgmoneta_append(r, "POST ");
+   }
+   else if (method == PGMONETA_HTTP_PUT)
+   {
+      r = pgmoneta_append(r, "PUT ");
+   }
+   else
+   {
+      pgmoneta_log_error("Invalid HTTP method: %d", method);
+      return 1;
+   }
+
+   r = pgmoneta_append(r, path);
+   r = pgmoneta_append(r, " HTTP/1.1\r\n");
+
+   *request = r;
+
+   return 0;
+}
+
+static int
+http_extract_headers_body(char* response, struct http* http)
+{
+   bool header = true;
+   char* p = NULL;
+   char* response_copy = NULL;
+
+   if (response == NULL)
+   {
+      pgmoneta_log_error("Response is NULL");
+      return 1;
+   }
+
+   response_copy = strdup(response);
+   if (response_copy == NULL)
+   {
+      pgmoneta_log_error("Failed to duplicate response string");
+      return 1;
+   }
+
+   p = strtok(response_copy, "\n");
+   while (p != NULL)
+   {
+      if (*p == '\r')
+      {
+         header = false;
+      }
+      else
+      {
+         if (!pgmoneta_is_number(p, 16))
+         {
+            if (header)
+            {
+               http->headers = pgmoneta_append(http->headers, p);
+               http->headers = pgmoneta_append_char(http->headers, '\n');
+            }
+            else
+            {
+               http->body = pgmoneta_append(http->body, p);
+               http->body = pgmoneta_append_char(http->body, '\n');
+            }
+         }
+      }
+
+      p = strtok(NULL, "\n");
+   }
+
+   free(response_copy);
+   return 0;
 }
 
 int
-pgmoneta_http_set_header_option(CURL* handle, struct curl_slist* chunk)
+pgmoneta_http_get(struct http* http, char* hostname, char* path)
 {
-   CURLcode res;
+   pgmoneta_log_debug("Starting pgmoneta_http_get");
+   struct message* msg_request = NULL;
+   int error = 0;
+   int status;
+   char* request = NULL;
+   char* full_request = NULL;
+   char* response = NULL;
+   char* user_agent = NULL;
+   char* endpoint = path ? path : "/get";
+
+   if (http_build_header(PGMONETA_HTTP_GET, endpoint, &request))
+   {
+      pgmoneta_log_error("Failed to build HTTP header");
+      goto error;
+   }
+
+   pgmoneta_http_add_header(http, "Host", hostname);
+   user_agent = pgmoneta_append(user_agent, "pgmoneta/");
+   user_agent = pgmoneta_append(user_agent, VERSION);
+   pgmoneta_http_add_header(http, "User-Agent", user_agent);
+   pgmoneta_http_add_header(http, "Accept", "text/*");
+   pgmoneta_http_add_header(http, "Connection", "close");
+
+   full_request = pgmoneta_append(NULL, request);
+   full_request = pgmoneta_append(full_request, http->request_headers);
+   full_request = pgmoneta_append(full_request, "\r\n");
+
+   msg_request = (struct message*)malloc(sizeof(struct message));
+   if (msg_request == NULL)
+   {
+      pgmoneta_log_error("Failed to allocate msg_request");
+      goto error;
+   }
+
+   memset(msg_request, 0, sizeof(struct message));
 
-   if (handle == NULL)
+   msg_request->data = full_request;
+   msg_request->length = strlen(full_request) + 1;
+
+   error = 0;
+req:
+   if (error < 5)
    {
+      status = pgmoneta_write_message(http->ssl, http->socket, msg_request);
+      if (status != MESSAGE_STATUS_OK)
+      {
+         error++;
+         pgmoneta_log_debug("Write failed, retrying (%d/5)", error);
+         goto req;
+      }
+   }
+   else
+   {
+      pgmoneta_log_error("Failed to write after 5 attempts");
       goto error;
    }
 
-   res = curl_easy_setopt(handle, CURLOPT_HTTPHEADER, chunk);
+   status = pgmoneta_http_read(http->ssl, http->socket, &response);
 
-   if (res != CURLE_OK)
+   if (response == NULL)
    {
+      pgmoneta_log_error("No response data collected");
       goto error;
    }
 
+   if (http_extract_headers_body(response, http))
+   {
+      pgmoneta_log_error("Failed to extract headers and body");
+      goto error;
+   }
+
+   pgmoneta_log_debug("HTTP Headers: %s", http->headers ? http->headers : "NULL");
+   pgmoneta_log_debug("HTTP Body: %s", http->body ? http->body : "NULL");
+
+   free(request);
+   free(full_request);
+   free(response);
+   free(msg_request);
+   free(user_agent);
+
+   free(http->request_headers);
+   http->request_headers = NULL;
+
    return 0;
 
 error:
+   pgmoneta_log_error("Error in pgmoneta_http_get, cleaning up");
+   if (request)
+   {
+      free(request);
+   }
+   if (full_request)
+   {
+      free(full_request);
+   }
+   if (response)
+   {
+      free(response);
+   }
+   if (msg_request)
+   {
+      free(msg_request);
+   }
+   if (user_agent)
+   {
+      free(user_agent);
+   }
+
+   free(http->request_headers);
+   http->request_headers = NULL;
 
    return 1;
 }
 
 int
-pgmoneta_http_set_request_option(CURL* handle, bool request_type)
+pgmoneta_http_connect(char* hostname, int port, bool secure, struct http** result)
+{
+   pgmoneta_log_debug("Connecting to %s:%d (secure: %d)", hostname, port, secure);
+   struct http* h = NULL;
+   int socket_fd = -1;
+   SSL* ssl = NULL;
+
+   h = (struct http*) malloc(sizeof(struct http));
+   if (h == NULL)
+   {
+      pgmoneta_log_error("Failed to allocate HTTP structure");
+      return 1;
+   }
+
+   memset(h, 0, sizeof(struct http));
+
+   if (pgmoneta_connect(hostname, port, &socket_fd))
+   {
+      pgmoneta_log_error("Failed to connect to %s:%d", hostname, port);
+      free(h);
+      return 1;
+   }
+
+   h->socket = socket_fd;
+
+   if (secure)
+   {
+      SSL_CTX* ctx = NULL;
+
+      if (pgmoneta_create_ssl_ctx(true, &ctx))
+      {
+         pgmoneta_log_error("Failed to create SSL context");
+         pgmoneta_disconnect(socket_fd);
+         free(h);
+         return 1;
+      }
+
+      if (SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION) == 0)
+      {
+         pgmoneta_log_error("Failed to set minimum TLS version");
+         SSL_CTX_free(ctx);
+         pgmoneta_disconnect(socket_fd);
+         free(h);
+         return 1;
+      }
+
+      ssl = SSL_new(ctx);
+      if (ssl == NULL)
+      {
+         pgmoneta_log_error("Failed to create SSL structure");
+         SSL_CTX_free(ctx);
+         pgmoneta_disconnect(socket_fd);
+         free(h);
+         return 1;
+      }
+
+      if (SSL_set_fd(ssl, socket_fd) == 0)
+      {
+         pgmoneta_log_error("Failed to set SSL file descriptor");
+         SSL_free(ssl);
+         SSL_CTX_free(ctx);
+         pgmoneta_disconnect(socket_fd);
+         free(h);
+         return 1;
+      }
+
+      int connect_result;
+      do
+      {
+         connect_result = SSL_connect(ssl);
+
+         if (connect_result != 1)
+         {
+            int err = SSL_get_error(ssl, connect_result);
+            switch (err)
+            {
+               case SSL_ERROR_WANT_READ:
+               case SSL_ERROR_WANT_WRITE:
+                  continue;
+               default:
+                  pgmoneta_log_error("SSL connection failed: %s", ERR_error_string(err, NULL));
+                  SSL_free(ssl);
+                  SSL_CTX_free(ctx);
+                  pgmoneta_disconnect(socket_fd);
+                  free(h);
+                  return 1;
+            }
+         }
+      }
+      while (connect_result != 1);
+
+      h->ssl = ssl;
+   }
+
+   *result = h;
+
+   return 0;
+}
+
+int
+pgmoneta_http_post(struct http* http, char* hostname, char* path, char* data, size_t length)
 {
-   CURLcode res = -1;
+   pgmoneta_log_debug("Starting pgmoneta_http_post");
+   struct message* msg_request = NULL;
+   int error = 0;
+   int status;
+   char* request = NULL;
+   char* full_request = NULL;
+   char* response = NULL;
+   char* user_agent = NULL;
+   char content_length[32];
+
+   if (http_build_header(PGMONETA_HTTP_POST, path, &request))
+   {
+      pgmoneta_log_error("Failed to build HTTP header");
+      goto error;
+   }
+
+   pgmoneta_http_add_header(http, "Host", hostname);
+   user_agent = pgmoneta_append(user_agent, "pgmoneta/");
+   user_agent = pgmoneta_append(user_agent, VERSION);
+   pgmoneta_http_add_header(http, "User-Agent", user_agent);
+   pgmoneta_http_add_header(http, "Connection", "close");
+
+   sprintf(content_length, "%zu", length);
+   pgmoneta_http_add_header(http, "Content-Length", content_length);
+   pgmoneta_http_add_header(http, "Content-Type", "application/x-www-form-urlencoded");
+
+   full_request = pgmoneta_append(NULL, request);
+   full_request = pgmoneta_append(full_request, http->request_headers);
+   full_request = pgmoneta_append(full_request, "\r\n");
 
-   if (handle == NULL)
+   if (data && length > 0)
    {
+      full_request = pgmoneta_append(full_request, data);
+   }
+
+   msg_request = (struct message*)malloc(sizeof(struct message));
+   if (msg_request == NULL)
+   {
+      pgmoneta_log_error("Failed to allocate msg_request");
       goto error;
    }
 
-   if (request_type == HTTP_GET)
+   memset(msg_request, 0, sizeof(struct message));
+
+   msg_request->data = full_request;
+   msg_request->length = strlen(full_request) + 1;
+
+   error = 0;
+req:
+   if (error < 5)
+   {
+      status = pgmoneta_write_message(http->ssl, http->socket, msg_request);
+      if (status != MESSAGE_STATUS_OK)
+      {
+         error++;
+         pgmoneta_log_debug("Write failed, retrying (%d/5)", error);
+         goto req;
+      }
+   }
+   else
    {
-      res = curl_easy_setopt(handle, CURLOPT_HTTPGET, 1L);
+      pgmoneta_log_error("Failed to write after 5 attempts");
+      goto error;
    }
-   else if (request_type == HTTP_PUT)
+
+   status = pgmoneta_http_read(http->ssl, http->socket, &response);
+
+   if (response == NULL)
    {
-      res = curl_easy_setopt(handle, CURLOPT_UPLOAD, 1L);
+      pgmoneta_log_error("No response data collected");
+      goto error;
    }
 
-   if (res != CURLE_OK)
+   if (http_extract_headers_body(response, http))
    {
+      pgmoneta_log_error("Failed to extract headers and body");
       goto error;
    }
 
+   free(request);
+   free(full_request);
+   free(response);
+   free(msg_request);
+   free(user_agent);
+
+   free(http->request_headers);
+   http->request_headers = NULL;
+
    return 0;
 
 error:
+   pgmoneta_log_error("Error in pgmoneta_http_post, cleaning up");
+   if (request)
+   {
+      free(request);
+   }
+   if (full_request)
+   {
+      free(full_request);
+   }
+   if (response)
+   {
+      free(response);
+   }
+   if (msg_request)
+   {
+      free(msg_request);
+   }
+   if (user_agent)
+   {
+      free(user_agent);
+   }
+
+   free(http->request_headers);
+   http->request_headers = NULL;
 
    return 1;
 }
 
 int
-pgmoneta_http_set_url_option(CURL* handle, char* url)
+pgmoneta_http_put(struct http* http, char* hostname, char* path, const void* data, size_t length)
 {
-   CURLcode res;
+   pgmoneta_log_debug("Starting pgmoneta_http_put");
+   struct message* msg_request = NULL;
+   int error = 0;
+   int status;
+   char* request = NULL;
+   char* full_request = NULL;
+   char* response = NULL;
+   char* user_agent = NULL;
+   char content_length[32];
+
+   if (http_build_header(PGMONETA_HTTP_PUT, path, &request))
+   {
+      pgmoneta_log_error("Failed to build HTTP header");
+      goto error;
+   }
+
+   pgmoneta_http_add_header(http, "Host", hostname);
+   user_agent = pgmoneta_append(user_agent, "pgmoneta/");
+   user_agent = pgmoneta_append(user_agent, VERSION);
+   pgmoneta_http_add_header(http, "User-Agent", user_agent);
+   pgmoneta_http_add_header(http, "Connection", "close");
 
-   if (handle == NULL)
+   sprintf(content_length, "%zu", length);
+   pgmoneta_http_add_header(http, "Content-Length", content_length);
+   pgmoneta_http_add_header(http, "Content-Type", "application/octet-stream");
+
+   full_request = pgmoneta_append(NULL, request);
+   full_request = pgmoneta_append(full_request, http->request_headers);
+   full_request = pgmoneta_append(full_request, "\r\n");
+
+   size_t headers_len = strlen(full_request);
+   size_t total_len = headers_len + length;
+
+   char* complete_request = malloc(total_len + 1);
+   if (complete_request == NULL)
+   {
+      pgmoneta_log_error("Failed to allocate complete request");
+      goto error;
+   }
+
+   memcpy(complete_request, full_request, headers_len);
+
+   if (data && length > 0)
+   {
+      memcpy(complete_request + headers_len, data, length);
+   }
+
+   complete_request[total_len] = '\0';
+
+   msg_request = (struct message*)malloc(sizeof(struct message));
+   if (msg_request == NULL)
    {
+      pgmoneta_log_error("Failed to allocate msg_request");
+      free(complete_request);
       goto error;
    }
 
-   res = curl_easy_setopt(handle, CURLOPT_URL, url);
+   memset(msg_request, 0, sizeof(struct message));
 
-   if (res != CURLE_OK)
+   msg_request->data = complete_request;
+   msg_request->length = total_len + 1;
+
+   error = 0;
+req:
+   if (error < 5)
    {
+      status = pgmoneta_write_message(http->ssl, http->socket, msg_request);
+      if (status != MESSAGE_STATUS_OK)
+      {
+         error++;
+         pgmoneta_log_debug("Write failed, retrying (%d/5)", error);
+         goto req;
+      }
+   }
+   else
+   {
+      pgmoneta_log_error("Failed to write after 5 attempts");
       goto error;
    }
 
+   status = pgmoneta_http_read(http->ssl, http->socket, &response);
+
+   if (response == NULL)
+   {
+      pgmoneta_log_error("No response data collected");
+      goto error;
+   }
+
+   if (http_extract_headers_body(response, http))
+   {
+      pgmoneta_log_error("Failed to extract headers and body");
+      goto error;
+   }
+
+   free(request);
+   free(full_request);
+   free(response);
+   free(msg_request->data);
+   free(msg_request);
+   free(user_agent);
+
+   free(http->request_headers);
+   http->request_headers = NULL;
+
    return 0;
 
 error:
+   pgmoneta_log_error("Error in pgmoneta_http_put, cleaning up");
+   if (request)
path: src/libpgmoneta/http.c
user: login: jesperpedersen
body: Guards
created_at: 2025-05-11T03:50:21Z
author_association: MEMBER
diff_hunk: @@ -29,99 +29,898 @@
 /* pgmoneta */
 #include <pgmoneta.h>
 #include <http.h>
+#include <logging.h>
+#include <network.h>
+#include <security.h>
 #include <utils.h>
 
-struct curl_slist*
-pgmoneta_http_add_header(struct curl_slist* chunk, char* header, char* value)
+/* system */
+#include <errno.h>
+#include <openssl/err.h>
+#include <unistd.h>
+
+static int http_build_header(int method, char* path, char** request);
+static int http_extract_headers_body(char* response, struct http* http);
+
+int
+pgmoneta_http_add_header(struct http* http, char* name, char* value)
 {
-   char* h = NULL;
+   char* tmp;
 
-   h = pgmoneta_append(h, header);
-   h = pgmoneta_append(h, ": ");
-   h = pgmoneta_append(h, value);
+   tmp = pgmoneta_append(http->request_headers, name);
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   chunk = curl_slist_append(chunk, h);
+   tmp = pgmoneta_append(http->request_headers, ": ");
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
+
+   tmp = pgmoneta_append(http->request_headers, value);
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   free(h);
+   tmp = pgmoneta_append(http->request_headers, "\r\n");
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   return chunk;
+   return 0;
+}
+
+static int
+http_build_header(int method, char* path, char** request)
+{
+   char* r = NULL;
+   *request = NULL;
+
+   if (method == PGMONETA_HTTP_GET)
+   {
+      r = pgmoneta_append(r, "GET ");
+   }
+   else if (method == PGMONETA_HTTP_POST)
+   {
+      r = pgmoneta_append(r, "POST ");
+   }
+   else if (method == PGMONETA_HTTP_PUT)
+   {
+      r = pgmoneta_append(r, "PUT ");
+   }
+   else
+   {
+      pgmoneta_log_error("Invalid HTTP method: %d", method);
+      return 1;
+   }
+
+   r = pgmoneta_append(r, path);
+   r = pgmoneta_append(r, " HTTP/1.1\r\n");
+
+   *request = r;
+
+   return 0;
+}
+
+static int
+http_extract_headers_body(char* response, struct http* http)
+{
+   bool header = true;
+   char* p = NULL;
+   char* response_copy = NULL;
+
+   if (response == NULL)
+   {
+      pgmoneta_log_error("Response is NULL");
+      return 1;
+   }
+
+   response_copy = strdup(response);
+   if (response_copy == NULL)
+   {
+      pgmoneta_log_error("Failed to duplicate response string");
+      return 1;
+   }
+
+   p = strtok(response_copy, "\n");
+   while (p != NULL)
+   {
+      if (*p == '\r')
+      {
+         header = false;
+      }
+      else
+      {
+         if (!pgmoneta_is_number(p, 16))
+         {
+            if (header)
+            {
+               http->headers = pgmoneta_append(http->headers, p);
+               http->headers = pgmoneta_append_char(http->headers, '\n');
+            }
+            else
+            {
+               http->body = pgmoneta_append(http->body, p);
+               http->body = pgmoneta_append_char(http->body, '\n');
+            }
+         }
+      }
+
+      p = strtok(NULL, "\n");
+   }
+
+   free(response_copy);
+   return 0;
 }
 
 int
-pgmoneta_http_set_header_option(CURL* handle, struct curl_slist* chunk)
+pgmoneta_http_get(struct http* http, char* hostname, char* path)
 {
-   CURLcode res;
+   pgmoneta_log_debug("Starting pgmoneta_http_get");
+   struct message* msg_request = NULL;
+   int error = 0;
+   int status;
+   char* request = NULL;
+   char* full_request = NULL;
+   char* response = NULL;
+   char* user_agent = NULL;
+   char* endpoint = path ? path : "/get";
+
+   if (http_build_header(PGMONETA_HTTP_GET, endpoint, &request))
+   {
+      pgmoneta_log_error("Failed to build HTTP header");
+      goto error;
+   }
+
+   pgmoneta_http_add_header(http, "Host", hostname);
+   user_agent = pgmoneta_append(user_agent, "pgmoneta/");
+   user_agent = pgmoneta_append(user_agent, VERSION);
+   pgmoneta_http_add_header(http, "User-Agent", user_agent);
+   pgmoneta_http_add_header(http, "Accept", "text/*");
+   pgmoneta_http_add_header(http, "Connection", "close");
+
+   full_request = pgmoneta_append(NULL, request);
+   full_request = pgmoneta_append(full_request, http->request_headers);
+   full_request = pgmoneta_append(full_request, "\r\n");
+
+   msg_request = (struct message*)malloc(sizeof(struct message));
+   if (msg_request == NULL)
+   {
+      pgmoneta_log_error("Failed to allocate msg_request");
+      goto error;
+   }
+
+   memset(msg_request, 0, sizeof(struct message));
 
-   if (handle == NULL)
+   msg_request->data = full_request;
+   msg_request->length = strlen(full_request) + 1;
+
+   error = 0;
+req:
+   if (error < 5)
    {
+      status = pgmoneta_write_message(http->ssl, http->socket, msg_request);
+      if (status != MESSAGE_STATUS_OK)
+      {
+         error++;
+         pgmoneta_log_debug("Write failed, retrying (%d/5)", error);
+         goto req;
+      }
+   }
+   else
+   {
+      pgmoneta_log_error("Failed to write after 5 attempts");
       goto error;
    }
 
-   res = curl_easy_setopt(handle, CURLOPT_HTTPHEADER, chunk);
+   status = pgmoneta_http_read(http->ssl, http->socket, &response);
 
-   if (res != CURLE_OK)
+   if (response == NULL)
    {
+      pgmoneta_log_error("No response data collected");
       goto error;
    }
 
+   if (http_extract_headers_body(response, http))
+   {
+      pgmoneta_log_error("Failed to extract headers and body");
+      goto error;
+   }
+
+   pgmoneta_log_debug("HTTP Headers: %s", http->headers ? http->headers : "NULL");
+   pgmoneta_log_debug("HTTP Body: %s", http->body ? http->body : "NULL");
+
+   free(request);
+   free(full_request);
+   free(response);
+   free(msg_request);
+   free(user_agent);
+
+   free(http->request_headers);
+   http->request_headers = NULL;
+
    return 0;
 
 error:
+   pgmoneta_log_error("Error in pgmoneta_http_get, cleaning up");
+   if (request)
+   {
+      free(request);
+   }
+   if (full_request)
+   {
+      free(full_request);
+   }
+   if (response)
+   {
+      free(response);
+   }
+   if (msg_request)
+   {
+      free(msg_request);
+   }
+   if (user_agent)
+   {
+      free(user_agent);
+   }
+
+   free(http->request_headers);
+   http->request_headers = NULL;
 
    return 1;
 }
 
 int
-pgmoneta_http_set_request_option(CURL* handle, bool request_type)
+pgmoneta_http_connect(char* hostname, int port, bool secure, struct http** result)
+{
+   pgmoneta_log_debug("Connecting to %s:%d (secure: %d)", hostname, port, secure);
+   struct http* h = NULL;
+   int socket_fd = -1;
+   SSL* ssl = NULL;
+
+   h = (struct http*) malloc(sizeof(struct http));
+   if (h == NULL)
+   {
+      pgmoneta_log_error("Failed to allocate HTTP structure");
+      return 1;
+   }
+
+   memset(h, 0, sizeof(struct http));
+
+   if (pgmoneta_connect(hostname, port, &socket_fd))
+   {
+      pgmoneta_log_error("Failed to connect to %s:%d", hostname, port);
+      free(h);
+      return 1;
+   }
+
+   h->socket = socket_fd;
+
+   if (secure)
+   {
+      SSL_CTX* ctx = NULL;
+
+      if (pgmoneta_create_ssl_ctx(true, &ctx))
+      {
+         pgmoneta_log_error("Failed to create SSL context");
+         pgmoneta_disconnect(socket_fd);
+         free(h);
+         return 1;
+      }
+
+      if (SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION) == 0)
+      {
+         pgmoneta_log_error("Failed to set minimum TLS version");
+         SSL_CTX_free(ctx);
+         pgmoneta_disconnect(socket_fd);
+         free(h);
+         return 1;
+      }
+
+      ssl = SSL_new(ctx);
+      if (ssl == NULL)
+      {
+         pgmoneta_log_error("Failed to create SSL structure");
+         SSL_CTX_free(ctx);
+         pgmoneta_disconnect(socket_fd);
+         free(h);
+         return 1;
+      }
+
+      if (SSL_set_fd(ssl, socket_fd) == 0)
+      {
+         pgmoneta_log_error("Failed to set SSL file descriptor");
+         SSL_free(ssl);
+         SSL_CTX_free(ctx);
+         pgmoneta_disconnect(socket_fd);
+         free(h);
+         return 1;
+      }
+
+      int connect_result;
+      do
+      {
+         connect_result = SSL_connect(ssl);
+
+         if (connect_result != 1)
+         {
+            int err = SSL_get_error(ssl, connect_result);
+            switch (err)
+            {
+               case SSL_ERROR_WANT_READ:
+               case SSL_ERROR_WANT_WRITE:
+                  continue;
+               default:
+                  pgmoneta_log_error("SSL connection failed: %s", ERR_error_string(err, NULL));
+                  SSL_free(ssl);
+                  SSL_CTX_free(ctx);
+                  pgmoneta_disconnect(socket_fd);
+                  free(h);
+                  return 1;
+            }
+         }
+      }
+      while (connect_result != 1);
+
+      h->ssl = ssl;
+   }
+
+   *result = h;
+
+   return 0;
+}
+
+int
+pgmoneta_http_post(struct http* http, char* hostname, char* path, char* data, size_t length)
 {
-   CURLcode res = -1;
+   pgmoneta_log_debug("Starting pgmoneta_http_post");
+   struct message* msg_request = NULL;
+   int error = 0;
+   int status;
+   char* request = NULL;
+   char* full_request = NULL;
+   char* response = NULL;
+   char* user_agent = NULL;
+   char content_length[32];
+
+   if (http_build_header(PGMONETA_HTTP_POST, path, &request))
+   {
+      pgmoneta_log_error("Failed to build HTTP header");
+      goto error;
+   }
+
+   pgmoneta_http_add_header(http, "Host", hostname);
+   user_agent = pgmoneta_append(user_agent, "pgmoneta/");
+   user_agent = pgmoneta_append(user_agent, VERSION);
+   pgmoneta_http_add_header(http, "User-Agent", user_agent);
+   pgmoneta_http_add_header(http, "Connection", "close");
+
+   sprintf(content_length, "%zu", length);
+   pgmoneta_http_add_header(http, "Content-Length", content_length);
+   pgmoneta_http_add_header(http, "Content-Type", "application/x-www-form-urlencoded");
+
+   full_request = pgmoneta_append(NULL, request);
+   full_request = pgmoneta_append(full_request, http->request_headers);
+   full_request = pgmoneta_append(full_request, "\r\n");
 
-   if (handle == NULL)
+   if (data && length > 0)
    {
+      full_request = pgmoneta_append(full_request, data);
+   }
+
+   msg_request = (struct message*)malloc(sizeof(struct message));
+   if (msg_request == NULL)
+   {
+      pgmoneta_log_error("Failed to allocate msg_request");
       goto error;
    }
 
-   if (request_type == HTTP_GET)
+   memset(msg_request, 0, sizeof(struct message));
+
+   msg_request->data = full_request;
+   msg_request->length = strlen(full_request) + 1;
+
+   error = 0;
+req:
+   if (error < 5)
+   {
+      status = pgmoneta_write_message(http->ssl, http->socket, msg_request);
+      if (status != MESSAGE_STATUS_OK)
+      {
+         error++;
+         pgmoneta_log_debug("Write failed, retrying (%d/5)", error);
+         goto req;
+      }
+   }
+   else
    {
-      res = curl_easy_setopt(handle, CURLOPT_HTTPGET, 1L);
+      pgmoneta_log_error("Failed to write after 5 attempts");
+      goto error;
    }
-   else if (request_type == HTTP_PUT)
+
+   status = pgmoneta_http_read(http->ssl, http->socket, &response);
+
+   if (response == NULL)
    {
-      res = curl_easy_setopt(handle, CURLOPT_UPLOAD, 1L);
+      pgmoneta_log_error("No response data collected");
+      goto error;
    }
 
-   if (res != CURLE_OK)
+   if (http_extract_headers_body(response, http))
    {
+      pgmoneta_log_error("Failed to extract headers and body");
       goto error;
    }
 
+   free(request);
+   free(full_request);
+   free(response);
+   free(msg_request);
+   free(user_agent);
+
+   free(http->request_headers);
+   http->request_headers = NULL;
+
    return 0;
 
 error:
+   pgmoneta_log_error("Error in pgmoneta_http_post, cleaning up");
+   if (request)
+   {
+      free(request);
+   }
+   if (full_request)
+   {
+      free(full_request);
+   }
+   if (response)
+   {
+      free(response);
+   }
+   if (msg_request)
+   {
+      free(msg_request);
+   }
+   if (user_agent)
+   {
+      free(user_agent);
+   }
+
+   free(http->request_headers);
+   http->request_headers = NULL;
 
    return 1;
 }
 
 int
-pgmoneta_http_set_url_option(CURL* handle, char* url)
+pgmoneta_http_put(struct http* http, char* hostname, char* path, const void* data, size_t length)
 {
-   CURLcode res;
+   pgmoneta_log_debug("Starting pgmoneta_http_put");
+   struct message* msg_request = NULL;
+   int error = 0;
+   int status;
+   char* request = NULL;
+   char* full_request = NULL;
+   char* response = NULL;
+   char* user_agent = NULL;
+   char content_length[32];
+
+   if (http_build_header(PGMONETA_HTTP_PUT, path, &request))
+   {
+      pgmoneta_log_error("Failed to build HTTP header");
+      goto error;
+   }
+
+   pgmoneta_http_add_header(http, "Host", hostname);
+   user_agent = pgmoneta_append(user_agent, "pgmoneta/");
+   user_agent = pgmoneta_append(user_agent, VERSION);
+   pgmoneta_http_add_header(http, "User-Agent", user_agent);
+   pgmoneta_http_add_header(http, "Connection", "close");
 
-   if (handle == NULL)
+   sprintf(content_length, "%zu", length);
+   pgmoneta_http_add_header(http, "Content-Length", content_length);
+   pgmoneta_http_add_header(http, "Content-Type", "application/octet-stream");
+
+   full_request = pgmoneta_append(NULL, request);
+   full_request = pgmoneta_append(full_request, http->request_headers);
+   full_request = pgmoneta_append(full_request, "\r\n");
+
+   size_t headers_len = strlen(full_request);
+   size_t total_len = headers_len + length;
+
+   char* complete_request = malloc(total_len + 1);
+   if (complete_request == NULL)
+   {
+      pgmoneta_log_error("Failed to allocate complete request");
+      goto error;
+   }
+
+   memcpy(complete_request, full_request, headers_len);
+
+   if (data && length > 0)
+   {
+      memcpy(complete_request + headers_len, data, length);
+   }
+
+   complete_request[total_len] = '\0';
+
+   msg_request = (struct message*)malloc(sizeof(struct message));
+   if (msg_request == NULL)
    {
+      pgmoneta_log_error("Failed to allocate msg_request");
+      free(complete_request);
       goto error;
    }
 
-   res = curl_easy_setopt(handle, CURLOPT_URL, url);
+   memset(msg_request, 0, sizeof(struct message));
 
-   if (res != CURLE_OK)
+   msg_request->data = complete_request;
+   msg_request->length = total_len + 1;
+
+   error = 0;
+req:
+   if (error < 5)
    {
+      status = pgmoneta_write_message(http->ssl, http->socket, msg_request);
+      if (status != MESSAGE_STATUS_OK)
+      {
+         error++;
+         pgmoneta_log_debug("Write failed, retrying (%d/5)", error);
+         goto req;
+      }
+   }
+   else
+   {
+      pgmoneta_log_error("Failed to write after 5 attempts");
       goto error;
    }
 
+   status = pgmoneta_http_read(http->ssl, http->socket, &response);
+
+   if (response == NULL)
+   {
+      pgmoneta_log_error("No response data collected");
+      goto error;
+   }
+
+   if (http_extract_headers_body(response, http))
+   {
+      pgmoneta_log_error("Failed to extract headers and body");
+      goto error;
+   }
+
+   free(request);
+   free(full_request);
+   free(response);
+   free(msg_request->data);
+   free(msg_request);
+   free(user_agent);
+
+   free(http->request_headers);
+   http->request_headers = NULL;
+
    return 0;
 
 error:
+   pgmoneta_log_error("Error in pgmoneta_http_put, cleaning up");
+   if (request)
+   {
+      free(request);
+   }
+   if (full_request)
+   {
+      free(full_request);
+   }
+   if (response)
+   {
+      free(response);
+   }
+   if (msg_request)
+   {
+      if (msg_request->data)
+      {
+         free(msg_request->data);
+      }
+      free(msg_request);
+   }
+   if (user_agent)
+   {
+      free(user_agent);
+   }
+
+   free(http->request_headers);
+   http->request_headers = NULL;
 
    return 1;
 }
+
+int
+pgmoneta_http_put_file(struct http* http, char* hostname, char* path, FILE* file, size_t file_size, char* content_type)
+{
+   pgmoneta_log_debug("Starting pgmoneta_http_put_file");
path: src/libpgmoneta/http.c
user: login: jesperpedersen
body: Again
created_at: 2025-05-11T03:50:31Z
author_association: MEMBER
diff_hunk: @@ -29,99 +29,898 @@
 /* pgmoneta */
 #include <pgmoneta.h>
 #include <http.h>
+#include <logging.h>
+#include <network.h>
+#include <security.h>
 #include <utils.h>
 
-struct curl_slist*
-pgmoneta_http_add_header(struct curl_slist* chunk, char* header, char* value)
+/* system */
+#include <errno.h>
+#include <openssl/err.h>
+#include <unistd.h>
+
+static int http_build_header(int method, char* path, char** request);
+static int http_extract_headers_body(char* response, struct http* http);
+
+int
+pgmoneta_http_add_header(struct http* http, char* name, char* value)
 {
-   char* h = NULL;
+   char* tmp;
 
-   h = pgmoneta_append(h, header);
-   h = pgmoneta_append(h, ": ");
-   h = pgmoneta_append(h, value);
+   tmp = pgmoneta_append(http->request_headers, name);
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   chunk = curl_slist_append(chunk, h);
+   tmp = pgmoneta_append(http->request_headers, ": ");
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
+
+   tmp = pgmoneta_append(http->request_headers, value);
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   free(h);
+   tmp = pgmoneta_append(http->request_headers, "\r\n");
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   return chunk;
+   return 0;
+}
+
+static int
+http_build_header(int method, char* path, char** request)
+{
+   char* r = NULL;
+   *request = NULL;
+
+   if (method == PGMONETA_HTTP_GET)
+   {
+      r = pgmoneta_append(r, "GET ");
+   }
+   else if (method == PGMONETA_HTTP_POST)
+   {
+      r = pgmoneta_append(r, "POST ");
+   }
+   else if (method == PGMONETA_HTTP_PUT)
+   {
+      r = pgmoneta_append(r, "PUT ");
+   }
+   else
+   {
+      pgmoneta_log_error("Invalid HTTP method: %d", method);
+      return 1;
+   }
+
+   r = pgmoneta_append(r, path);
+   r = pgmoneta_append(r, " HTTP/1.1\r\n");
+
+   *request = r;
+
+   return 0;
+}
+
+static int
+http_extract_headers_body(char* response, struct http* http)
+{
+   bool header = true;
+   char* p = NULL;
+   char* response_copy = NULL;
+
+   if (response == NULL)
+   {
+      pgmoneta_log_error("Response is NULL");
+      return 1;
+   }
+
+   response_copy = strdup(response);
+   if (response_copy == NULL)
+   {
+      pgmoneta_log_error("Failed to duplicate response string");
+      return 1;
+   }
+
+   p = strtok(response_copy, "\n");
+   while (p != NULL)
+   {
+      if (*p == '\r')
+      {
+         header = false;
+      }
+      else
+      {
+         if (!pgmoneta_is_number(p, 16))
+         {
+            if (header)
+            {
+               http->headers = pgmoneta_append(http->headers, p);
+               http->headers = pgmoneta_append_char(http->headers, '\n');
+            }
+            else
+            {
+               http->body = pgmoneta_append(http->body, p);
+               http->body = pgmoneta_append_char(http->body, '\n');
+            }
+         }
+      }
+
+      p = strtok(NULL, "\n");
+   }
+
+   free(response_copy);
+   return 0;
 }
 
 int
-pgmoneta_http_set_header_option(CURL* handle, struct curl_slist* chunk)
+pgmoneta_http_get(struct http* http, char* hostname, char* path)
 {
-   CURLcode res;
+   pgmoneta_log_debug("Starting pgmoneta_http_get");
+   struct message* msg_request = NULL;
+   int error = 0;
+   int status;
+   char* request = NULL;
+   char* full_request = NULL;
+   char* response = NULL;
+   char* user_agent = NULL;
+   char* endpoint = path ? path : "/get";
+
+   if (http_build_header(PGMONETA_HTTP_GET, endpoint, &request))
+   {
+      pgmoneta_log_error("Failed to build HTTP header");
+      goto error;
+   }
+
+   pgmoneta_http_add_header(http, "Host", hostname);
+   user_agent = pgmoneta_append(user_agent, "pgmoneta/");
+   user_agent = pgmoneta_append(user_agent, VERSION);
+   pgmoneta_http_add_header(http, "User-Agent", user_agent);
+   pgmoneta_http_add_header(http, "Accept", "text/*");
+   pgmoneta_http_add_header(http, "Connection", "close");
+
+   full_request = pgmoneta_append(NULL, request);
+   full_request = pgmoneta_append(full_request, http->request_headers);
+   full_request = pgmoneta_append(full_request, "\r\n");
+
+   msg_request = (struct message*)malloc(sizeof(struct message));
+   if (msg_request == NULL)
+   {
+      pgmoneta_log_error("Failed to allocate msg_request");
+      goto error;
+   }
+
+   memset(msg_request, 0, sizeof(struct message));
 
-   if (handle == NULL)
+   msg_request->data = full_request;
+   msg_request->length = strlen(full_request) + 1;
+
+   error = 0;
+req:
+   if (error < 5)
    {
+      status = pgmoneta_write_message(http->ssl, http->socket, msg_request);
+      if (status != MESSAGE_STATUS_OK)
+      {
+         error++;
+         pgmoneta_log_debug("Write failed, retrying (%d/5)", error);
+         goto req;
+      }
+   }
+   else
+   {
+      pgmoneta_log_error("Failed to write after 5 attempts");
       goto error;
    }
 
-   res = curl_easy_setopt(handle, CURLOPT_HTTPHEADER, chunk);
+   status = pgmoneta_http_read(http->ssl, http->socket, &response);
 
-   if (res != CURLE_OK)
+   if (response == NULL)
    {
+      pgmoneta_log_error("No response data collected");
       goto error;
    }
 
+   if (http_extract_headers_body(response, http))
+   {
+      pgmoneta_log_error("Failed to extract headers and body");
+      goto error;
+   }
+
+   pgmoneta_log_debug("HTTP Headers: %s", http->headers ? http->headers : "NULL");
+   pgmoneta_log_debug("HTTP Body: %s", http->body ? http->body : "NULL");
+
+   free(request);
+   free(full_request);
+   free(response);
+   free(msg_request);
+   free(user_agent);
+
+   free(http->request_headers);
+   http->request_headers = NULL;
+
    return 0;
 
 error:
+   pgmoneta_log_error("Error in pgmoneta_http_get, cleaning up");
+   if (request)
+   {
+      free(request);
+   }
+   if (full_request)
+   {
+      free(full_request);
+   }
+   if (response)
+   {
+      free(response);
+   }
+   if (msg_request)
+   {
+      free(msg_request);
+   }
+   if (user_agent)
+   {
+      free(user_agent);
+   }
+
+   free(http->request_headers);
+   http->request_headers = NULL;
 
    return 1;
 }
 
 int
-pgmoneta_http_set_request_option(CURL* handle, bool request_type)
+pgmoneta_http_connect(char* hostname, int port, bool secure, struct http** result)
+{
+   pgmoneta_log_debug("Connecting to %s:%d (secure: %d)", hostname, port, secure);
+   struct http* h = NULL;
+   int socket_fd = -1;
+   SSL* ssl = NULL;
+
+   h = (struct http*) malloc(sizeof(struct http));
+   if (h == NULL)
+   {
+      pgmoneta_log_error("Failed to allocate HTTP structure");
+      return 1;
+   }
+
+   memset(h, 0, sizeof(struct http));
+
+   if (pgmoneta_connect(hostname, port, &socket_fd))
+   {
+      pgmoneta_log_error("Failed to connect to %s:%d", hostname, port);
+      free(h);
+      return 1;
+   }
+
+   h->socket = socket_fd;
+
+   if (secure)
+   {
+      SSL_CTX* ctx = NULL;
+
+      if (pgmoneta_create_ssl_ctx(true, &ctx))
+      {
+         pgmoneta_log_error("Failed to create SSL context");
+         pgmoneta_disconnect(socket_fd);
+         free(h);
+         return 1;
+      }
+
+      if (SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION) == 0)
+      {
+         pgmoneta_log_error("Failed to set minimum TLS version");
+         SSL_CTX_free(ctx);
+         pgmoneta_disconnect(socket_fd);
+         free(h);
+         return 1;
+      }
+
+      ssl = SSL_new(ctx);
+      if (ssl == NULL)
+      {
+         pgmoneta_log_error("Failed to create SSL structure");
+         SSL_CTX_free(ctx);
+         pgmoneta_disconnect(socket_fd);
+         free(h);
+         return 1;
+      }
+
+      if (SSL_set_fd(ssl, socket_fd) == 0)
+      {
+         pgmoneta_log_error("Failed to set SSL file descriptor");
+         SSL_free(ssl);
+         SSL_CTX_free(ctx);
+         pgmoneta_disconnect(socket_fd);
+         free(h);
+         return 1;
+      }
+
+      int connect_result;
+      do
+      {
+         connect_result = SSL_connect(ssl);
+
+         if (connect_result != 1)
+         {
+            int err = SSL_get_error(ssl, connect_result);
+            switch (err)
+            {
+               case SSL_ERROR_WANT_READ:
+               case SSL_ERROR_WANT_WRITE:
+                  continue;
+               default:
+                  pgmoneta_log_error("SSL connection failed: %s", ERR_error_string(err, NULL));
+                  SSL_free(ssl);
+                  SSL_CTX_free(ctx);
+                  pgmoneta_disconnect(socket_fd);
+                  free(h);
+                  return 1;
+            }
+         }
+      }
+      while (connect_result != 1);
+
+      h->ssl = ssl;
+   }
+
+   *result = h;
+
+   return 0;
+}
+
+int
+pgmoneta_http_post(struct http* http, char* hostname, char* path, char* data, size_t length)
 {
-   CURLcode res = -1;
+   pgmoneta_log_debug("Starting pgmoneta_http_post");
+   struct message* msg_request = NULL;
+   int error = 0;
+   int status;
+   char* request = NULL;
+   char* full_request = NULL;
+   char* response = NULL;
+   char* user_agent = NULL;
+   char content_length[32];
+
+   if (http_build_header(PGMONETA_HTTP_POST, path, &request))
+   {
+      pgmoneta_log_error("Failed to build HTTP header");
+      goto error;
+   }
+
+   pgmoneta_http_add_header(http, "Host", hostname);
+   user_agent = pgmoneta_append(user_agent, "pgmoneta/");
+   user_agent = pgmoneta_append(user_agent, VERSION);
+   pgmoneta_http_add_header(http, "User-Agent", user_agent);
+   pgmoneta_http_add_header(http, "Connection", "close");
+
+   sprintf(content_length, "%zu", length);
+   pgmoneta_http_add_header(http, "Content-Length", content_length);
+   pgmoneta_http_add_header(http, "Content-Type", "application/x-www-form-urlencoded");
+
+   full_request = pgmoneta_append(NULL, request);
+   full_request = pgmoneta_append(full_request, http->request_headers);
+   full_request = pgmoneta_append(full_request, "\r\n");
 
-   if (handle == NULL)
+   if (data && length > 0)
    {
+      full_request = pgmoneta_append(full_request, data);
+   }
+
+   msg_request = (struct message*)malloc(sizeof(struct message));
+   if (msg_request == NULL)
+   {
+      pgmoneta_log_error("Failed to allocate msg_request");
       goto error;
    }
 
-   if (request_type == HTTP_GET)
+   memset(msg_request, 0, sizeof(struct message));
+
+   msg_request->data = full_request;
+   msg_request->length = strlen(full_request) + 1;
+
+   error = 0;
+req:
+   if (error < 5)
+   {
+      status = pgmoneta_write_message(http->ssl, http->socket, msg_request);
+      if (status != MESSAGE_STATUS_OK)
+      {
+         error++;
+         pgmoneta_log_debug("Write failed, retrying (%d/5)", error);
+         goto req;
+      }
+   }
+   else
    {
-      res = curl_easy_setopt(handle, CURLOPT_HTTPGET, 1L);
+      pgmoneta_log_error("Failed to write after 5 attempts");
+      goto error;
    }
-   else if (request_type == HTTP_PUT)
+
+   status = pgmoneta_http_read(http->ssl, http->socket, &response);
+
+   if (response == NULL)
    {
-      res = curl_easy_setopt(handle, CURLOPT_UPLOAD, 1L);
+      pgmoneta_log_error("No response data collected");
+      goto error;
    }
 
-   if (res != CURLE_OK)
+   if (http_extract_headers_body(response, http))
    {
+      pgmoneta_log_error("Failed to extract headers and body");
       goto error;
    }
 
+   free(request);
+   free(full_request);
+   free(response);
+   free(msg_request);
+   free(user_agent);
+
+   free(http->request_headers);
+   http->request_headers = NULL;
+
    return 0;
 
 error:
+   pgmoneta_log_error("Error in pgmoneta_http_post, cleaning up");
+   if (request)
+   {
+      free(request);
+   }
+   if (full_request)
+   {
+      free(full_request);
+   }
+   if (response)
+   {
+      free(response);
+   }
+   if (msg_request)
+   {
+      free(msg_request);
+   }
+   if (user_agent)
+   {
+      free(user_agent);
+   }
+
+   free(http->request_headers);
+   http->request_headers = NULL;
 
    return 1;
 }
 
 int
-pgmoneta_http_set_url_option(CURL* handle, char* url)
+pgmoneta_http_put(struct http* http, char* hostname, char* path, const void* data, size_t length)
 {
-   CURLcode res;
+   pgmoneta_log_debug("Starting pgmoneta_http_put");
+   struct message* msg_request = NULL;
+   int error = 0;
+   int status;
+   char* request = NULL;
+   char* full_request = NULL;
+   char* response = NULL;
+   char* user_agent = NULL;
+   char content_length[32];
+
+   if (http_build_header(PGMONETA_HTTP_PUT, path, &request))
+   {
+      pgmoneta_log_error("Failed to build HTTP header");
+      goto error;
+   }
+
+   pgmoneta_http_add_header(http, "Host", hostname);
+   user_agent = pgmoneta_append(user_agent, "pgmoneta/");
+   user_agent = pgmoneta_append(user_agent, VERSION);
+   pgmoneta_http_add_header(http, "User-Agent", user_agent);
+   pgmoneta_http_add_header(http, "Connection", "close");
 
-   if (handle == NULL)
+   sprintf(content_length, "%zu", length);
+   pgmoneta_http_add_header(http, "Content-Length", content_length);
+   pgmoneta_http_add_header(http, "Content-Type", "application/octet-stream");
+
+   full_request = pgmoneta_append(NULL, request);
+   full_request = pgmoneta_append(full_request, http->request_headers);
+   full_request = pgmoneta_append(full_request, "\r\n");
+
+   size_t headers_len = strlen(full_request);
+   size_t total_len = headers_len + length;
+
+   char* complete_request = malloc(total_len + 1);
+   if (complete_request == NULL)
+   {
+      pgmoneta_log_error("Failed to allocate complete request");
+      goto error;
+   }
+
+   memcpy(complete_request, full_request, headers_len);
+
+   if (data && length > 0)
+   {
+      memcpy(complete_request + headers_len, data, length);
+   }
+
+   complete_request[total_len] = '\0';
+
+   msg_request = (struct message*)malloc(sizeof(struct message));
+   if (msg_request == NULL)
    {
+      pgmoneta_log_error("Failed to allocate msg_request");
+      free(complete_request);
       goto error;
    }
 
-   res = curl_easy_setopt(handle, CURLOPT_URL, url);
+   memset(msg_request, 0, sizeof(struct message));
 
-   if (res != CURLE_OK)
+   msg_request->data = complete_request;
+   msg_request->length = total_len + 1;
+
+   error = 0;
+req:
+   if (error < 5)
    {
+      status = pgmoneta_write_message(http->ssl, http->socket, msg_request);
+      if (status != MESSAGE_STATUS_OK)
+      {
+         error++;
+         pgmoneta_log_debug("Write failed, retrying (%d/5)", error);
+         goto req;
+      }
+   }
+   else
+   {
+      pgmoneta_log_error("Failed to write after 5 attempts");
       goto error;
    }
 
+   status = pgmoneta_http_read(http->ssl, http->socket, &response);
+
+   if (response == NULL)
+   {
+      pgmoneta_log_error("No response data collected");
+      goto error;
+   }
+
+   if (http_extract_headers_body(response, http))
+   {
+      pgmoneta_log_error("Failed to extract headers and body");
+      goto error;
+   }
+
+   free(request);
+   free(full_request);
+   free(response);
+   free(msg_request->data);
+   free(msg_request);
+   free(user_agent);
+
+   free(http->request_headers);
+   http->request_headers = NULL;
+
    return 0;
 
 error:
+   pgmoneta_log_error("Error in pgmoneta_http_put, cleaning up");
+   if (request)
+   {
+      free(request);
+   }
+   if (full_request)
+   {
+      free(full_request);
+   }
+   if (response)
+   {
+      free(response);
+   }
+   if (msg_request)
+   {
+      if (msg_request->data)
+      {
+         free(msg_request->data);
+      }
+      free(msg_request);
+   }
+   if (user_agent)
+   {
+      free(user_agent);
+   }
+
+   free(http->request_headers);
+   http->request_headers = NULL;
 
    return 1;
 }
+
+int
+pgmoneta_http_put_file(struct http* http, char* hostname, char* path, FILE* file, size_t file_size, char* content_type)
+{
+   pgmoneta_log_debug("Starting pgmoneta_http_put_file");
+   struct message* msg_request = NULL;
+   int error = 0;
+   int status;
+   char* request = NULL;
+   char* header_part = NULL;
+   char* response = NULL;
+   char* user_agent = NULL;
+   char content_length[32];
+   void* file_buffer = NULL;
+
+   if (file == NULL)
+   {
+      pgmoneta_log_error("File is NULL");
+      goto error;
+   }
+
+   if (http_build_header(PGMONETA_HTTP_PUT, path, &request))
+   {
+      pgmoneta_log_error("Failed to build HTTP header");
+      goto error;
+   }
+
+   pgmoneta_http_add_header(http, "Host", hostname);
+   user_agent = pgmoneta_append(user_agent, "pgmoneta/");
+   user_agent = pgmoneta_append(user_agent, VERSION);
+   pgmoneta_http_add_header(http, "User-Agent", user_agent);
+   pgmoneta_http_add_header(http, "Connection", "close");
+
+   sprintf(content_length, "%zu", file_size);
+   pgmoneta_http_add_header(http, "Content-Length", content_length);
+
+   /* default to application/octet-stream if not specified */
+   char* type = content_type ? content_type : "application/octet-stream";
+   pgmoneta_http_add_header(http, "Content-Type", type);
+
+   header_part = pgmoneta_append(NULL, request);
+   header_part = pgmoneta_append(header_part, http->request_headers);
+   header_part = pgmoneta_append(header_part, "\r\n");
+
+   pgmoneta_log_debug("File size: %zu", file_size);
+
+   rewind(file);
+
+   file_buffer = malloc(file_size);
+   if (file_buffer == NULL)
+   {
+      pgmoneta_log_error("Failed to allocate memory for file content: %zu bytes", file_size);
+      goto error;
+   }
+
+   size_t bytes_read = fread(file_buffer, 1, file_size, file);
+   if (bytes_read != file_size)
+   {
+      pgmoneta_log_error("Failed to read entire file. Expected %zu bytes, got %zu", file_size, bytes_read);
+      goto error;
+   }
+
+   pgmoneta_log_debug("Read %zu bytes from file", bytes_read);
+
+   msg_request = (struct message*)malloc(sizeof(struct message));
+   if (msg_request == NULL)
+   {
+      pgmoneta_log_error("Failed to allocate msg_request");
+      goto error;
+   }
+
+   memset(msg_request, 0, sizeof(struct message));
+
+   size_t header_len = strlen(header_part);
+   size_t total_len = header_len + file_size;
+
+   char* full_request = malloc(total_len + 1);
+   if (full_request == NULL)
+   {
+      pgmoneta_log_error("Failed to allocate memory for full request: %zu bytes", total_len + 1);
+      goto error;
+   }
+
+   memcpy(full_request, header_part, header_len);
+
+   memcpy(full_request + header_len, file_buffer, file_size);
+
+   full_request[total_len] = '\0';
+
+   pgmoneta_log_debug("Setting msg_request data, total size: %zu", total_len);
+   msg_request->data = full_request;
+   msg_request->length = total_len;
+
+   error = 0;
+req:
+   if (error < 5)
+   {
+      status = pgmoneta_write_message(http->ssl, http->socket, msg_request);
+      if (status != MESSAGE_STATUS_OK)
+      {
+         error++;
+         pgmoneta_log_debug("Write failed, retrying (%d/5)", error);
+         goto req;
+      }
+   }
+   else
+   {
+      pgmoneta_log_error("Failed to write after 5 attempts");
+      goto error;
+   }
+
+   status = pgmoneta_http_read(http->ssl, http->socket, &response);
+
+   if (response == NULL)
+   {
+      pgmoneta_log_error("No response data collected");
+      goto error;
+   }
+
+   if (http_extract_headers_body(response, http))
+   {
+      pgmoneta_log_error("Failed to extract headers and body");
+      goto error;
+   }
+
+   int status_code = 0;
+   if (http->headers && sscanf(http->headers, "HTTP/1.1 %d", &status_code) == 1)
+   {
+      pgmoneta_log_debug("HTTP status code: %d", status_code);
+      if (status_code >= 200 && status_code < 300)
+      {
+         pgmoneta_log_debug("HTTP request successful");
+      }
+      else
+      {
+         pgmoneta_log_error("HTTP request failed with status code: %d", status_code);
+      }
+   }
+
+   free(request);
+   free(header_part);
+   free(response);
+   free(file_buffer);
+   free(full_request);
+   free(msg_request);
+   free(user_agent);
+
+   free(http->request_headers);
+   http->request_headers = NULL;
+
+   return (status_code >= 200 && status_code < 300) ? 0 : 1;
+
+error:
+   pgmoneta_log_error("Error in pgmoneta_http_put_file, cleaning up");
+   if (request)
path: src/libpgmoneta/http.c
user: login: jesperpedersen
body: Guards
created_at: 2025-05-11T03:50:58Z
author_association: MEMBER
diff_hunk: @@ -29,99 +29,818 @@
 /* pgmoneta */
 #include <pgmoneta.h>
 #include <http.h>
+#include <logging.h>
+#include <network.h>
+#include <security.h>
 #include <utils.h>
 
-struct curl_slist*
-pgmoneta_http_add_header(struct curl_slist* chunk, char* header, char* value)
+/* system */
+#include <errno.h>
+#include <unistd.h>
+#include <openssl/err.h>
+
+static int http_build_header(int method, char* path, char** request);
+static int http_extract_headers_body(char* response, struct http* http);
+
+int
+pgmoneta_http_add_header(struct http* http, char* name, char* value)
 {
-   char* h = NULL;
+   char* tmp;
 
-   h = pgmoneta_append(h, header);
-   h = pgmoneta_append(h, ": ");
-   h = pgmoneta_append(h, value);
+   tmp = pgmoneta_append(http->request_headers, name);
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   chunk = curl_slist_append(chunk, h);
+   tmp = pgmoneta_append(http->request_headers, ": ");
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
 
-   free(h);
+   tmp = pgmoneta_append(http->request_headers, value);
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
+
+   tmp = pgmoneta_append(http->request_headers, "\r\n");
+   if (tmp == NULL)
+   {
+      return 1;
+   }
+   http->request_headers = tmp;
+
+   return 0;
+}
+
+static int
+http_build_header(int method, char* path, char** request)
+{
+   char* r = NULL;
+   *request = NULL;
+
+   if (method == PGMONETA_HTTP_GET)
+   {
+      r = pgmoneta_append(r, "GET ");
+   }
+   else if (method == PGMONETA_HTTP_POST)
+   {
+      r = pgmoneta_append(r, "POST ");
+   }
+   else if (method == PGMONETA_HTTP_PUT)
+   {
+      r = pgmoneta_append(r, "PUT ");
+   }
+   else
+   {
+      pgmoneta_log_error("Invalid HTTP method: %d", method);
+      return 1;
+   }
+
+   r = pgmoneta_append(r, path);
+   r = pgmoneta_append(r, " HTTP/1.1\r\n");
+
+   *request = r;
+
+   return 0;
+}
+
+static int
+http_extract_headers_body(char* response, struct http* http)
+{
+   bool header = true;
+   char* p = NULL;
+   char* response_copy = NULL;
+
+   if (response == NULL)
+   {
+      pgmoneta_log_error("Response is NULL");
+      return 1;
+   }
+
+   response_copy = strdup(response);
+   if (response_copy == NULL)
+   {
+      pgmoneta_log_error("Failed to duplicate response string");
+      return 1;
+   }
+
+   p = strtok(response_copy, "\n");
+   while (p != NULL)
+   {
+      if (*p == '\r')
+      {
+         header = false;
+      }
+      else
+      {
+         if (!pgmoneta_is_number(p, 16))
+         {
+            if (header)
+            {
+               http->headers = pgmoneta_append(http->headers, p);
+               http->headers = pgmoneta_append_char(http->headers, '\n');
+            }
+            else
+            {
+               http->body = pgmoneta_append(http->body, p);
+               http->body = pgmoneta_append_char(http->body, '\n');
+            }
+         }
+      }
+
+      p = strtok(NULL, "\n");
+   }
+
+   free(response_copy);
+   return 0;
+}
+
+int
+pgmoneta_http_get(struct http* http, char* hostname, char* path)
+{
+   struct message* msg_request = NULL;
+   int error = 0;
+   int status;
+   char* request = NULL;
+   char* full_request = NULL;
+   char* response = NULL;
+   char* user_agent = NULL;
+   char* endpoint = path ? path : "/get";
+
+   pgmoneta_log_debug("Starting pgmoneta_http_get");
+   if (http_build_header(PGMONETA_HTTP_GET, endpoint, &request))
+   {
+      pgmoneta_log_error("Failed to build HTTP header");
+      goto error;
+   }
+
+   pgmoneta_http_add_header(http, "Host", hostname);
+   user_agent = pgmoneta_append(user_agent, "pgmoneta/");
+   user_agent = pgmoneta_append(user_agent, VERSION);
+   pgmoneta_http_add_header(http, "User-Agent", user_agent);
+   pgmoneta_http_add_header(http, "Accept", "text/*");
+   pgmoneta_http_add_header(http, "Connection", "close");
+
+   full_request = pgmoneta_append(NULL, request);
+   full_request = pgmoneta_append(full_request, http->request_headers);
+   full_request = pgmoneta_append(full_request, "\r\n");
+
+   msg_request = (struct message*)malloc(sizeof(struct message));
+   if (msg_request == NULL)
+   {
+      pgmoneta_log_error("Failed to allocate msg_request");
+      goto error;
+   }
 
-   return chunk;
+   memset(msg_request, 0, sizeof(struct message));
+
+   msg_request->data = full_request;
+   msg_request->length = strlen(full_request) + 1;
+
+   error = 0;
+req:
+   if (error < 5)
+   {
+      status = pgmoneta_write_message(http->ssl, http->socket, msg_request);
+      if (status != MESSAGE_STATUS_OK)
+      {
+         error++;
+         pgmoneta_log_debug("Write failed, retrying (%d/5)", error);
+         goto req;
+      }
+   }
+   else
+   {
+      pgmoneta_log_error("Failed to write after 5 attempts");
+      goto error;
+   }
+
+   status = pgmoneta_http_read(http->ssl, http->socket, &response);
+
+   if (response == NULL)
+   {
+      pgmoneta_log_error("No response data collected");
+      goto error;
+   }
+
+   if (http_extract_headers_body(response, http))
+   {
+      pgmoneta_log_error("Failed to extract headers and body");
+      goto error;
+   }
+
+   pgmoneta_log_debug("HTTP Headers: %s", http->headers ? http->headers : "NULL");
+   pgmoneta_log_debug("HTTP Body: %s", http->body ? http->body : "NULL");
+
+   free(request);
+   free(full_request);
+   free(response);
+   free(msg_request);
+   free(user_agent);
+
+   free(http->request_headers);
+   http->request_headers = NULL;
+
+   return 0;
+
+error:
+   free(request);
+   free(full_request);
+   free(response);
+   free(msg_request);
+   free(user_agent);
+   free(http->request_headers);
+   http->request_headers = NULL;
+   return 1;
 }
 
 int
-pgmoneta_http_set_header_option(CURL* handle, struct curl_slist* chunk)
+pgmoneta_http_connect(char* hostname, int port, bool secure, struct http** result)
 {
-   CURLcode res;
+   struct http* h = NULL;
+   int socket_fd = -1;
+   SSL* ssl = NULL;
+   SSL_CTX* ctx = NULL;
 
-   if (handle == NULL)
+   pgmoneta_log_debug("Connecting to %s:%d (secure: %d)", hostname, port, secure);
+   h = (struct http*) malloc(sizeof(struct http));
+   if (h == NULL)
    {
+      pgmoneta_log_error("Failed to allocate HTTP structure");
       goto error;
    }
 
-   res = curl_easy_setopt(handle, CURLOPT_HTTPHEADER, chunk);
+   memset(h, 0, sizeof(struct http));
 
-   if (res != CURLE_OK)
+   if (pgmoneta_connect(hostname, port, &socket_fd))
    {
+      pgmoneta_log_error("Failed to connect to %s:%d", hostname, port);
       goto error;
    }
 
+   h->socket = socket_fd;
+
+   if (secure)
+   {
+      if (pgmoneta_create_ssl_ctx(true, &ctx))
+      {
+         pgmoneta_log_error("Failed to create SSL context");
+         goto error;
+      }
+
+      if (SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION) == 0)
+      {
+         pgmoneta_log_error("Failed to set minimum TLS version");
+         goto error;
+      }
+
+      ssl = SSL_new(ctx);
+      if (ssl == NULL)
+      {
+         pgmoneta_log_error("Failed to create SSL structure");
+         goto error;
+      }
+
+      if (SSL_set_fd(ssl, socket_fd) == 0)
+      {
+         pgmoneta_log_error("Failed to set SSL file descriptor");
+         goto error;
+      }
+
+      int connect_result;
+      do
+      {
+         connect_result = SSL_connect(ssl);
+
+         if (connect_result != 1)
+         {
+            int err = SSL_get_error(ssl, connect_result);
+            switch (err)
+            {
+               case SSL_ERROR_WANT_READ:
+               case SSL_ERROR_WANT_WRITE:
+                  continue;
+               default:
+                  pgmoneta_log_error("SSL connection failed: %s", ERR_error_string(err, NULL));
+                  goto error;
+            }
+         }
+      }
+      while (connect_result != 1);
+
+      h->ssl = ssl;
+   }
+
+   *result = h;
+
    return 0;
 
 error:
+   if (ssl != NULL)
+   {
+      SSL_free(ssl);
+   }
+   if (ctx != NULL)
+   {
+      SSL_CTX_free(ctx);
+   }
+   if (socket_fd != -1)
+   {
+      pgmoneta_disconnect(socket_fd);
+   }
+   free(h);
+   return 1;
+}
+
+int
+pgmoneta_http_post(struct http* http, char* hostname, char* path, char* data, size_t length)
+{
+   pgmoneta_log_debug("Starting pgmoneta_http_post");
path: src/libpgmoneta/http.c
user: login: jesperpedersen
body: One here
created_at: 2025-05-11T15:25:06Z
author_association: MEMBER

PR comments:
user: login: jesperpedersen
created_at: 2025-05-04T12:35:27Z
body: @bassamadnan Just keep working on it, and PTAL me once done
author_association: MEMBER
user: login: bassamadnan
created_at: 2025-05-06T11:20:18Z
body: @jesperpedersen PTAL
author_association: COLLABORATOR
user: login: jesperpedersen
created_at: 2025-05-06T12:14:53Z
body: @bassamadnan Please, squash
author_association: MEMBER
user: login: jesperpedersen
created_at: 2025-05-06T13:20:46Z
body: @bassamadnan Please, do `git grep -i curl`
author_association: MEMBER
user: login: jesperpedersen
created_at: 2025-05-06T13:21:33Z
body: Some of them you need, some you don't
author_association: MEMBER
user: login: jesperpedersen
created_at: 2025-05-06T14:14:45Z
body: @ashu3103 Can you take a quick look at the test suite stuff ?
author_association: MEMBER
user: login: bassamadnan
created_at: 2025-05-06T15:09:38Z
body: > @ashu3103 Can you take a quick look at the test suite stuff ?

Feel free to run [these](https://github.com/bassamadnan/pgmoneta/blob/8422733ee40ebb93d60caf1bad97f1ada141eca8/src/libpgmoneta/test.c) , uncomment [these lines](https://github.com/bassamadnan/pgmoneta/blob/8422733ee40ebb93d60caf1bad97f1ada141eca8/src/main.c#L682) to create a fork which runs the functions. @ashu3103 
author_association: COLLABORATOR
user: login: bassamadnan
created_at: 2025-05-09T16:54:42Z
body: @ashu3103 hi, were you able to make time to check this out? I'll start porting it meanwhile, let me know incase any mishap were to arise.
author_association: COLLABORATOR
user: login: ashu3103
created_at: 2025-05-09T16:58:38Z
body: > @ashu3103 hi, were you able to make time to check this out? I'll start porting it meanwhile, let me know incase any mishap were to arise.

@bassamadnan, @jesperpedersen, The testsuite looks good to me. @bassamadnan you can take a look at the reviewed changes, rest everything seems in place :)
author_association: COLLABORATOR
user: login: bassamadnan
created_at: 2025-05-10T04:39:37Z
body: @ashu3103 thanks!, i think you could review it one more time I tried to address your reviewed changes wherever I could @jesperpedersen 
author_association: COLLABORATOR
user: login: ashu3103
created_at: 2025-05-10T04:46:43Z
body: @bassamadnan, I think you might have forgotten to push the changes. Please check once
author_association: COLLABORATOR
user: login: bassamadnan
created_at: 2025-05-10T04:51:46Z
body: > @bassamadnan, I think you might have forgotten to push the changes. Please check once

Nope I had made those changes immediately after this comment:



> Feel free to run [these](https://github.com/bassamadnan/pgmoneta/blob/8422733ee40ebb93d60caf1bad97f1ada141eca8/src/libpgmoneta/test.c) , uncomment [these lines](https://github.com/bassamadnan/pgmoneta/blob/8422733ee40ebb93d60caf1bad97f1ada141eca8/src/main.c#L682) to create a fork which runs the functions. @ashu3103

On commit hash [d8d8aab](https://github.com/pgmoneta/pgmoneta/commit/d8d8aabe3dbfac7027c16a8049015e8f3eb2069d), they were independent of the http logic so that remains the same.

author_association: COLLABORATOR
user: login: ashu3103
created_at: 2025-05-10T07:30:42Z
body: @bassamadnan, I could not see the changes suggested in your changed files maybe I am missing something?
author_association: COLLABORATOR
user: login: bassamadnan
created_at: 2025-05-10T13:07:09Z
body: @ashu3103 hmm im not sure why. Are you checking from phone perhaps?  I addressed the requested changes wherever I could and they do show the tag of "Outdated" as well.
author_association: COLLABORATOR
user: login: ashu3103
created_at: 2025-05-10T13:22:21Z
body: > @ashu3103 hmm im not sure why. Are you checking from phone perhaps? I addressed the requested changes wherever I could and they do show the tag of "Outdated" as well.

@bassamadnan, strange its showing `Pending` for me. @jesperpedersen, can you PTAL.
author_association: COLLABORATOR
user: login: jesperpedersen
created_at: 2025-05-11T03:52:22Z
body: @bassamadnan Lets start with these
author_association: MEMBER
user: login: jesperpedersen
created_at: 2025-05-11T15:28:51Z
body: @bassamadnan See if some of the _debug should be _trace instead
author_association: MEMBER
user: login: bassamadnan
created_at: 2025-05-11T15:41:06Z
body: @jesperpedersen yup, will take a look
author_association: COLLABORATOR
user: login: bassamadnan
created_at: 2025-05-11T20:04:16Z
body: @jesperpedersen I have changed all the entry point statements to trace, some byte tracking logs have also been shifted to trace since I feel like its mostly used in that sense. hope this looks fine
author_association: COLLABORATOR
user: login: jesperpedersen
created_at: 2025-05-11T21:01:01Z
body: Merged.

Thanks for your contribution !
author_association: MEMBER
user: login: jesperpedersen
created_at: 2025-05-11T21:01:14Z
body: @bassamadnan Please, port to pgexporter
author_association: MEMBER