fwupd/plugins/redfish/fu-redfish-request.c
2021-09-07 17:25:37 +01:00

320 lines
8.8 KiB
C

/*
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include "fu-redfish-request.h"
struct _FuRedfishRequest {
GObject parent_instance;
CURL *curl;
#ifdef HAVE_LIBCURL_7_62_0
CURLU *uri;
#else
gchar *uri_base;
#endif
GByteArray *buf;
glong status_code;
JsonParser *json_parser;
JsonObject *json_obj;
GHashTable *cache; /* nullable */
};
G_DEFINE_TYPE(FuRedfishRequest, fu_redfish_request, G_TYPE_OBJECT)
typedef gchar curlptr;
G_DEFINE_AUTOPTR_CLEANUP_FUNC(curlptr, curl_free)
JsonObject *
fu_redfish_request_get_json_object(FuRedfishRequest *self)
{
g_return_val_if_fail(FU_IS_REDFISH_REQUEST(self), NULL);
return self->json_obj;
}
CURL *
fu_redfish_request_get_curl(FuRedfishRequest *self)
{
g_return_val_if_fail(FU_IS_REDFISH_REQUEST(self), NULL);
return self->curl;
}
#ifdef HAVE_LIBCURL_7_62_0
CURLU *
fu_redfish_request_get_uri(FuRedfishRequest *self)
{
g_return_val_if_fail(FU_IS_REDFISH_REQUEST(self), NULL);
return self->uri;
}
#else
void
fu_redfish_request_set_uri_base(FuRedfishRequest *self, const gchar *uri_base)
{
g_return_if_fail(FU_IS_REDFISH_REQUEST(self));
self->uri_base = g_strdup(uri_base);
}
#endif
glong
fu_redfish_request_get_status_code(FuRedfishRequest *self)
{
g_return_val_if_fail(FU_IS_REDFISH_REQUEST(self), G_MAXLONG);
return self->status_code;
}
static gboolean
fu_redfish_request_load_json(FuRedfishRequest *self, GByteArray *buf, GError **error)
{
JsonNode *json_root;
/* load */
if (buf->data == NULL || buf->len == 0) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"there was no JSON payload");
return FALSE;
}
if (!json_parser_load_from_data(self->json_parser,
(const gchar *)buf->data,
(gssize)buf->len,
error)) {
return FALSE;
}
json_root = json_parser_get_root(self->json_parser);
if (json_root == NULL) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"no JSON root node");
return FALSE;
}
self->json_obj = json_node_get_object(json_root);
if (self->json_obj == NULL) {
g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no JSON object");
return FALSE;
}
/* dump for humans */
if (g_getenv("FWUPD_REDFISH_VERBOSE") != NULL) {
g_autoptr(GString) str = g_string_new(NULL);
g_autoptr(JsonGenerator) json_generator = json_generator_new();
json_generator_set_pretty(json_generator, TRUE);
json_generator_set_root(json_generator, json_root);
json_generator_to_gstring(json_generator, str);
g_debug("response: %s", str->str);
}
/* unauthorized */
if (json_object_has_member(self->json_obj, "error")) {
FwupdError error_code = FWUPD_ERROR_INTERNAL;
JsonObject *json_error;
const gchar *id = NULL;
const gchar *msg = "Unknown failure";
/* extended error present */
json_error = json_object_get_object_member(self->json_obj, "error");
if (json_object_has_member(json_error, "@Message.ExtendedInfo")) {
JsonArray *json_error_array;
json_error_array =
json_object_get_array_member(json_error, "@Message.ExtendedInfo");
if (json_array_get_length(json_error_array) > 0) {
JsonObject *json_error2;
json_error2 = json_array_get_object_element(json_error_array, 0);
if (json_object_has_member(json_error2, "MessageId"))
id =
json_object_get_string_member(json_error2, "MessageId");
if (json_object_has_member(json_error2, "Message"))
msg = json_object_get_string_member(json_error2, "Message");
}
} else {
if (json_object_has_member(json_error, "code"))
id = json_object_get_string_member(json_error, "code");
if (json_object_has_member(json_error, "message"))
msg = json_object_get_string_member(json_error, "message");
}
if (g_strcmp0(id, "Base.1.8.AccessDenied") == 0)
error_code = FWUPD_ERROR_AUTH_FAILED;
g_set_error_literal(error, FWUPD_ERROR, error_code, msg);
return FALSE;
}
/* success */
return TRUE;
}
gboolean
fu_redfish_request_perform(FuRedfishRequest *self,
const gchar *path,
FuRedfishRequestPerformFlags flags,
GError **error)
{
CURLcode res;
#ifdef HAVE_LIBCURL_7_62_0
g_autoptr(curlptr) uri_str = NULL;
#else
g_autofree gchar *uri_str = NULL;
#endif
g_return_val_if_fail(FU_IS_REDFISH_REQUEST(self), FALSE);
g_return_val_if_fail(path != NULL, FALSE);
g_return_val_if_fail(self->status_code == 0, FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
/* already in cache? */
if (flags & FU_REDFISH_REQUEST_PERFORM_FLAG_USE_CACHE && self->cache != NULL) {
GByteArray *buf = g_hash_table_lookup(self->cache, path);
if (buf != NULL) {
if (flags & FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON)
return fu_redfish_request_load_json(self, buf, error);
g_byte_array_unref(self->buf);
self->buf = g_byte_array_ref(buf);
return TRUE;
}
}
/* do request */
#ifdef HAVE_LIBCURL_7_62_0
curl_url_set(self->uri, CURLUPART_PATH, path, 0);
curl_url_get(self->uri, CURLUPART_URL, &uri_str, 0);
#else
uri_str = g_strdup_printf("%s%s", self->uri_base, path);
if (curl_easy_setopt(self->curl, CURLOPT_URL, uri_str) != CURLE_OK) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"failed to create message for URI");
return FALSE;
}
#endif
res = curl_easy_perform(self->curl);
curl_easy_getinfo(self->curl, CURLINFO_RESPONSE_CODE, &self->status_code);
if (g_getenv("FWUPD_REDFISH_VERBOSE") != NULL) {
g_autofree gchar *str = NULL;
str = g_strndup((const gchar *)self->buf->data, self->buf->len);
g_debug("%s: %s [%li]", uri_str, str, self->status_code);
}
/* check result */
if (res != CURLE_OK) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"failed to request %s: %s",
uri_str,
curl_easy_strerror(res));
return FALSE;
}
/* load JSON */
if (flags & FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON) {
if (!fu_redfish_request_load_json(self, self->buf, error)) {
g_prefix_error(error, "failed to parse %s: ", uri_str);
return FALSE;
}
}
/* save to cache */
if (self->cache != NULL && path != NULL) {
g_hash_table_insert(self->cache, g_strdup(path), g_byte_array_ref(self->buf));
}
/* success */
return TRUE;
}
gboolean
fu_redfish_request_patch(FuRedfishRequest *self,
const gchar *path,
JsonBuilder *builder,
FuRedfishRequestPerformFlags flags,
GError **error)
{
struct curl_slist *hs = NULL;
g_autoptr(GString) str = g_string_new(NULL);
g_autoptr(JsonGenerator) json_generator = json_generator_new();
g_autoptr(JsonNode) json_root = NULL;
/* export as a string */
json_root = json_builder_get_root(builder);
json_generator_set_pretty(json_generator, TRUE);
json_generator_set_root(json_generator, json_root);
json_generator_to_gstring(json_generator, str);
if (g_getenv("FWUPD_REDFISH_VERBOSE") != NULL)
g_debug("request: %s", str->str);
/* patch */
curl_easy_setopt(self->curl, CURLOPT_CUSTOMREQUEST, "PATCH");
curl_easy_setopt(self->curl, CURLOPT_POSTFIELDS, str->str);
curl_easy_setopt(self->curl, CURLOPT_POSTFIELDSIZE, (long)str->len);
hs = curl_slist_append(hs, "Content-Type: application/json");
curl_easy_setopt(self->curl, CURLOPT_HTTPHEADER, hs);
return fu_redfish_request_perform(self, path, flags, error);
}
static size_t
fu_redfish_request_write_cb(char *ptr, size_t size, size_t nmemb, void *userdata)
{
GByteArray *buf = (GByteArray *)userdata;
gsize realsize = size * nmemb;
g_byte_array_append(buf, (const guint8 *)ptr, realsize);
return realsize;
}
void
fu_redfish_request_set_cache(FuRedfishRequest *self, GHashTable *cache)
{
g_return_if_fail(FU_IS_REDFISH_REQUEST(self));
g_return_if_fail(cache != NULL);
g_return_if_fail(self->cache == NULL);
self->cache = g_hash_table_ref(cache);
}
void
fu_redfish_request_set_curlsh(FuRedfishRequest *self, CURLSH *curlsh)
{
g_return_if_fail(FU_IS_REDFISH_REQUEST(self));
g_return_if_fail(curlsh != NULL);
curl_easy_setopt(self->curl, CURLOPT_SHARE, curlsh);
}
static void
fu_redfish_request_init(FuRedfishRequest *self)
{
self->curl = curl_easy_init();
#ifdef HAVE_LIBCURL_7_62_0
self->uri = curl_url();
#endif
self->buf = g_byte_array_new();
self->json_parser = json_parser_new();
curl_easy_setopt(self->curl, CURLOPT_WRITEFUNCTION, fu_redfish_request_write_cb);
curl_easy_setopt(self->curl, CURLOPT_WRITEDATA, self->buf);
}
static void
fu_redfish_request_finalize(GObject *object)
{
FuRedfishRequest *self = FU_REDFISH_REQUEST(object);
if (self->cache != NULL)
g_hash_table_unref(self->cache);
g_object_unref(self->json_parser);
g_byte_array_unref(self->buf);
curl_easy_cleanup(self->curl);
#ifdef HAVE_LIBCURL_7_62_0
curl_url_cleanup(self->uri);
#else
g_free(self->uri_base);
#endif
G_OBJECT_CLASS(fu_redfish_request_parent_class)->finalize(object);
}
static void
fu_redfish_request_class_init(FuRedfishRequestClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS(klass);
object_class->finalize = fu_redfish_request_finalize;
}