--- /dev/null
+/*#############################################################################
+# #
+# telemetryd - The IPFire Telemetry Collection Service #
+# Copyright (C) 2026 IPFire Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#include <errno.h>
+#include <stdlib.h>
+
+#include <curl/curl.h>
+
+#include "buffer.h"
+#include "client.h"
+#include "ctx.h"
+#include "request.h"
+
+struct td_request {
+ td_ctx* ctx;
+ int nrefs;
+
+ // HTTP Client
+ td_client* client;
+
+ // cURL handle
+ CURL* handle;
+
+ // URL
+ CURLU* url;
+
+ // Output Buffer
+ td_buffer* buffer;
+};
+
+static int td_request_debug_callback(CURL *handle, curl_infotype type,
+ char* data, size_t size, void* p) {
+ td_request* self = p;
+
+ switch (type) {
+ case CURLINFO_TEXT:
+ DEBUG(self->ctx, "cURL: %.*s", (int)size, data);
+ break;
+
+ // Log headers
+ case CURLINFO_HEADER_IN:
+ DEBUG(self->ctx, "cURL: < %.*s", (int)size, data);
+ break;
+
+ case CURLINFO_HEADER_OUT:
+ DEBUG(self->ctx, "cURL: > %.*s", (int)size, data);
+ break;
+
+ // Ignore everything else
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static size_t td_request_write(char* data, size_t size, size_t nmemb, void* p) {
+ td_request* self = p;
+
+ // Write everything to the buffer
+ return td_buffer_write(self->buffer, data, nmemb);
+}
+
+static int td_request_setup(td_request* self) {
+ int r;
+
+ // XXX Share Handle
+
+ // Be a good net citizen and set a user agent
+ r = curl_easy_setopt(self->handle, CURLOPT_USERAGENT, "Mozilla/5.0 (compatible; "
+ PACKAGE_NAME "/" PACKAGE_VERSION "; +https://www.ipfire.org/)");
+ if (r)
+ goto ERROR;
+
+ // Enable logging/debugging
+ r = curl_easy_setopt(self->handle, CURLOPT_VERBOSE, 1L);
+ if (r)
+ goto ERROR;
+
+ r = curl_easy_setopt(self->handle, CURLOPT_DEBUGFUNCTION, td_request_debug_callback);
+ if (r)
+ goto ERROR;
+
+ r = curl_easy_setopt(self->handle, CURLOPT_DEBUGDATA, self);
+ if (r)
+ goto ERROR;
+
+ // Raise any HTTP errors
+ r = curl_easy_setopt(self->handle, CURLOPT_FAILONERROR, 1L);
+ if (r)
+ goto ERROR;
+
+ // Allow all support encodings
+ r = curl_easy_setopt(self->handle, CURLOPT_ACCEPT_ENCODING, "");
+ if (r)
+ goto ERROR;
+
+ // Reference back to this request
+ r = curl_easy_setopt(self->handle, CURLOPT_PRIVATE, self);
+ if (r)
+ goto ERROR;
+
+ // Follow any redirects
+ r = curl_easy_setopt(self->handle, CURLOPT_FOLLOWLOCATION, 1L);
+ if (r)
+ goto ERROR;
+
+ // Only follow up to 30 redirects
+ r = curl_easy_setopt(self->handle, CURLOPT_MAXREDIRS, 30L);
+ if (r)
+ goto ERROR;
+
+ // Write all data to the callback function
+ r = curl_easy_setopt(self->handle, CURLOPT_WRITEFUNCTION, td_request_write);
+ if (r)
+ goto ERROR;
+
+ r = curl_easy_setopt(self->handle, CURLOPT_WRITEDATA, self);
+ if (r)
+ goto ERROR;
+
+ // Configure the URL
+ r = curl_easy_setopt(self->handle, CURLOPT_CURLU, self->url);
+ if (r)
+ goto ERROR;
+
+ERROR:
+ return r;
+}
+
+static void td_request_free(td_request* self) {
+ if (self->handle)
+ curl_easy_cleanup(self->handle);
+ if (self->buffer)
+ td_buffer_unref(self->buffer);
+ if (self->client)
+ td_client_unref(self->client);
+ if (self->url)
+ curl_url_cleanup(self->url);
+ if (self->ctx)
+ td_ctx_unref(self->ctx);
+ free(self);
+}
+
+int td_request_create(td_request** request, td_ctx* ctx, td_client* client) {
+ td_request* self = NULL;
+ int r;
+
+ // Allocate some memory
+ self = calloc(1, sizeof(*self));
+ if (!self)
+ return -errno;
+
+ // Store a reference to the context
+ self->ctx = td_ctx_ref(ctx);
+
+ // Initialize the reference counter
+ self->nrefs = 1;
+
+ // Store a reference to the client
+ self->client = td_client_ref(client);
+
+ // Allocate a handle
+ self->handle = curl_easy_init();
+ if (!self->handle) {
+ r = -ENOMEM;
+ goto ERROR;
+ }
+
+ // Allocate the URL
+ self->url = curl_url();
+ if (!self->url) {
+ ERROR(self->ctx, "Failed to allocate the URL: %m\n");
+ r = -errno;
+ goto ERROR;
+ }
+
+ // Allocate an output buffer
+ r = td_buffer_create(&self->buffer, self->ctx);
+ if (r < 0)
+ goto ERROR;
+
+ // Setup the request
+ r = td_request_setup(self);
+ if (r)
+ goto ERROR;
+
+ // Return the pointer
+ *request = self;
+ return 0;
+
+ERROR:
+ td_request_free(self);
+
+ return r;
+}
+
+td_request* td_request_ref(td_request* self) {
+ ++self->nrefs;
+ return self;
+}
+
+td_request* td_request_unref(td_request* self) {
+ if (--self->nrefs > 0)
+ return self;
+
+ td_request_free(self);
+ return NULL;
+}
+
+char* td_request_get_url(td_request* self) {
+ char* url = NULL;
+ int r;
+
+ // Format the URL
+ r = curl_url_get(self->url, CURLUPART_URL, &url, 0);
+ if (r < 0)
+ return NULL;
+
+ return url;
+}
+
+int td_request_set_url(td_request* self, const char* url) {
+ int r;
+
+ // Store the URL
+ r = curl_url_set(self->url, CURLUPART_URL, url, 0);
+ if (r < 0) {
+ ERROR(self->ctx, "Could not set the URL: %s\n", curl_easy_strerror(r));
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+CURL* td_request_handle(td_request* self) {
+ return self->handle;
+}
+
+int td_request_launch(td_request* self) {
+ return td_client_launch_request(self->client, self);
+}
+
+int td_request_done(td_request* self, int code) {
+ return 0;
+}
--- /dev/null
+/*#############################################################################
+# #
+# telemetryd - The IPFire Telemetry Collection Service #
+# Copyright (C) 2026 IPFire Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#ifndef TELEMETRY_REQUEST_H
+#define TELEMETRY_REQUEST_H
+
+#include <curl/curl.h>
+
+typedef struct td_request td_request;
+
+#include "client.h"
+#include "ctx.h"
+
+int td_request_create(td_request** request, td_ctx* ctx, td_client* client);
+
+td_request* td_request_ref(td_request* self);
+td_request* td_request_unref(td_request* self);
+
+char* td_request_get_url(td_request* self);
+int td_request_set_url(td_request* self, const char* url);
+
+CURL* td_request_handle(td_request* self);
+
+int td_request_launch(td_request* self);
+int td_request_done(td_request* self, int code);
+
+#endif /* TELEMETRY_REQUEST_H */