From 1cf3858ba15605293933355d41750dc92567ff85 Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Fri, 7 Mar 2025 21:04:20 -0700 Subject: [PATCH] util/http: Add simple wrapper functions for libcurl --- include/qemu/http.h | 46 +++++++++++++ util/http.c | 157 ++++++++++++++++++++++++++++++++++++++++++++ util/meson.build | 1 + 3 files changed, 204 insertions(+) create mode 100644 include/qemu/http.h create mode 100644 util/http.c diff --git a/include/qemu/http.h b/include/qemu/http.h new file mode 100644 index 0000000000..b6fb11bbc1 --- /dev/null +++ b/include/qemu/http.h @@ -0,0 +1,46 @@ +/* + * Simple HTTP handlers + * + * Copyright (c) 2025 Matt Borgerson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#ifndef QEMU_HTTP_H +#define QEMU_HTTP_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct http_progress_cb_info { + int (*progress)(struct http_progress_cb_info *progress_info); + void *userptr; + size_t dlnow; + size_t dltotal; + size_t ulnow; + size_t ultotal; +} http_progress_cb_info; + +int http_get(const char *url, GByteArray *response_body, + http_progress_cb_info *progress_info, Error **errp); +int http_post_json(const char *url, const char *json_data, Error **errp); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/util/http.c b/util/http.c new file mode 100644 index 0000000000..de51514498 --- /dev/null +++ b/util/http.c @@ -0,0 +1,157 @@ +/* + * Simple HTTP handlers + * + * Copyright (c) 2025 Matt Borgerson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "qemu/http.h" + +#include +#include + +// Ignore SSL certificate verification (for self-signed certs) +#define ALLOW_INSECURE_HOSTS 0 + +static bool libcurl_init_called = false; +static bool libcurl_init_success = false; + +static bool ensure_libcurl_initialized(Error **errp) +{ + if (!libcurl_init_called) { + CURLcode res = curl_global_init(CURL_GLOBAL_ALL); + libcurl_init_called = true; + if (res == CURLE_OK) { + libcurl_init_success = true; + atexit(curl_global_cleanup); + } + } + + if (!libcurl_init_success) { + error_setg(errp, "curl_global_init failed"); + } + + return libcurl_init_success; +} + +static int http_progress_cb(void *clientp, curl_off_t dltotal, curl_off_t dlnow, + curl_off_t ultotal, curl_off_t ulnow) +{ + http_progress_cb_info *info = clientp; + + info->dlnow = dlnow; + info->dltotal = dltotal; + + info->ulnow = ulnow; + info->ultotal = ultotal; + + return info->progress(info); +} + +static size_t http_get_cb(void *ptr, size_t size, size_t nmemb, void *userdata) +{ + assert(size == 1); // Per CURLOPT_WRITEFUNCTION spec + if (userdata) { + g_byte_array_append((GByteArray *)userdata, ptr, nmemb); + } + return nmemb; +} + +int http_get(const char *url, GByteArray *response_body, + http_progress_cb_info *progress_info, Error **errp) +{ + if (!ensure_libcurl_initialized(errp)) { + return -1; + } + + CURL *curl = curl_easy_init(); + if (!curl) { + error_setg(errp, "curl_easy_init failed"); + return -1; + } + + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, http_get_cb); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, response_body); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); // Follow redirects +#if ALLOW_INSECURE_HOSTS + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); +#endif + if (progress_info) { + curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, http_progress_cb); + curl_easy_setopt(curl, CURLOPT_XFERINFODATA, (void *)progress_info); + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); + } + + long http_response_code = -1; + + CURLcode res = curl_easy_perform(curl); + if (res == CURLE_OK) { + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_response_code); + } else { + error_setg(errp, "curl_easy_perform failed with code %d", res); + } + + curl_easy_cleanup(curl); + + return http_response_code; +} + +int http_post_json(const char *url, const char *json_data, Error **errp) +{ + if (!ensure_libcurl_initialized(errp)) { + return -1; + } + + CURL *curl = curl_easy_init(); + if (!curl) { + error_setg(errp, "curl_easy_init failed"); + return -1; + } + + struct curl_slist *headers = + curl_slist_append(NULL, "Content-Type: application/json"); + if (!headers) { + error_setg(errp, "curl_slist_append failed"); + curl_easy_cleanup(curl); + return -1; + } + + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_POST, 1L); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_data); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); +#if ALLOW_INSECURE_HOSTS + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); +#endif + + long http_response_code = -1; + + CURLcode res = curl_easy_perform(curl); + if (res == CURLE_OK) { + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_response_code); + } else { + error_setg(errp, "curl_easy_perform failed with code %d", res); + } + + curl_slist_free_all(headers); + curl_easy_cleanup(curl); + + return http_response_code; +} diff --git a/util/meson.build b/util/meson.build index 9b7056e8f9..94fc72d554 100644 --- a/util/meson.build +++ b/util/meson.build @@ -136,3 +136,4 @@ elif cpu in ['riscv32', 'riscv64'] util_ss.add(files('cpuinfo-riscv.c')) endif util_ss.add(files('sha1.c','rc4.c')) +util_ss.add([curl, files('http.c')])