/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ /* * Copyright © 2012 – 2017 Red Hat, Inc. * * 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 "config.h" #include #include "goahttpclient.h" #include "goasouplogger.h" #include "goautils.h" struct _GoaHttpClient { GObject parent_instance; }; G_DEFINE_TYPE (GoaHttpClient, goa_http_client, G_TYPE_OBJECT); /* ---------------------------------------------------------------------------------------------------- */ static void goa_http_client_init (GoaHttpClient *self) { } static void goa_http_client_class_init (GoaHttpClientClass *klass) { } /* ---------------------------------------------------------------------------------------------------- */ GoaHttpClient * goa_http_client_new (void) { return GOA_HTTP_CLIENT (g_object_new (GOA_TYPE_HTTP_CLIENT, NULL)); } /* ---------------------------------------------------------------------------------------------------- */ typedef struct { GCancellable *cancellable; SoupMessage *msg; SoupSession *session; gboolean accept_ssl_errors; gulong cancellable_id; } CheckData; typedef struct { gchar *password; gchar *username; } CheckAuthData; static void http_client_check_data_free (gpointer user_data) { CheckData *data = user_data; if (data->cancellable_id > 0) { g_cancellable_disconnect (data->cancellable, data->cancellable_id); g_object_unref (data->cancellable); } /* soup_session_queue_message stole the references to data->msg */ g_object_unref (data->session); g_slice_free (CheckData, data); } static void http_client_check_auth_data_free (gpointer data, GClosure *closure) { CheckAuthData *auth = data; g_free (auth->password); g_free (auth->username); g_slice_free (CheckAuthData, auth); } static void http_client_authenticate (SoupSession *session, SoupMessage *msg, SoupAuth *auth, gboolean retrying, gpointer user_data) { CheckAuthData *data = user_data; if (retrying) return; soup_auth_authenticate (auth, data->username, data->password); } static void http_client_request_started (SoupSession *session, SoupMessage *msg, SoupSocket *socket, gpointer user_data) { CheckData *data; GTask *task = G_TASK (user_data); GError *error; GTlsCertificateFlags cert_flags; data = g_task_get_task_data (task); error = NULL; if (!data->accept_ssl_errors && soup_message_get_https_status (msg, NULL, &cert_flags) && cert_flags != 0) { goa_utils_set_error_ssl (&error, cert_flags); g_task_return_error (task, error); soup_session_abort (data->session); } } static void http_client_check_cancelled_cb (GCancellable *cancellable, gpointer user_data) { CheckData *data; GTask *task = G_TASK (user_data); gboolean cancelled; data = g_task_get_task_data (task); cancelled = g_task_return_error_if_cancelled (task); soup_session_abort (data->session); g_return_if_fail (cancelled); } static gboolean http_client_check_free_in_idle (gpointer user_data) { GTask *task = G_TASK (user_data); g_object_unref (task); return G_SOURCE_REMOVE; } static void http_client_check_response_cb (SoupSession *session, SoupMessage *msg, gpointer user_data) { GError *error; GMainContext *context; GSource *source; GTask *task = G_TASK (user_data); error = NULL; /* status == SOUP_STATUS_CANCELLED, if we are being aborted by the * GCancellable or due to an SSL error. The GTask was already * 'returned' by the respective callbacks. */ if (msg->status_code == SOUP_STATUS_CANCELLED) goto out; else if (msg->status_code != SOUP_STATUS_OK) { g_warning ("goa_http_client_check() failed: %u — %s", msg->status_code, msg->reason_phrase); goa_utils_set_error_soup (&error, msg); g_task_return_error (task, error); goto out; } g_task_return_boolean (task, TRUE); out: /* We might be invoked from a GCancellable::cancelled * handler, and unreffing the GTask will disconnect the * handler. Since disconnecting from inside the handler will cause a * deadlock [1], we use an idle handler to break them up. * * [1] https://bugzilla.gnome.org/show_bug.cgi?id=705395 */ source = g_idle_source_new (); g_source_set_priority (source, G_PRIORITY_DEFAULT_IDLE); g_source_set_callback (source, http_client_check_free_in_idle, task, NULL); g_source_set_name (source, "[goa] http_client_check_free_in_idle"); context = g_task_get_context (task); g_source_attach (source, context); g_source_unref (source); } void goa_http_client_check (GoaHttpClient *self, const gchar *uri, const gchar *username, const gchar *password, gboolean accept_ssl_errors, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { CheckData *data; CheckAuthData *auth; GTask *task; SoupLogger *logger; g_return_if_fail (GOA_IS_HTTP_CLIENT (self)); g_return_if_fail (uri != NULL && uri[0] != '\0'); g_return_if_fail (username != NULL && username[0] != '\0'); g_return_if_fail (password != NULL && password[0] != '\0'); g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); task = g_task_new (self, cancellable, callback, user_data); g_task_set_source_tag (task, goa_http_client_check); data = g_slice_new0 (CheckData); g_task_set_task_data (task, data, http_client_check_data_free); data->session = soup_session_new_with_options (SOUP_SESSION_SSL_STRICT, FALSE, NULL); logger = goa_soup_logger_new (SOUP_LOGGER_LOG_BODY, -1); soup_session_add_feature (data->session, SOUP_SESSION_FEATURE (logger)); g_object_unref (logger); data->accept_ssl_errors = accept_ssl_errors; data->msg = soup_message_new (SOUP_METHOD_GET, uri); if (cancellable != NULL) { data->cancellable = g_object_ref (cancellable); data->cancellable_id = g_cancellable_connect (cancellable, G_CALLBACK (http_client_check_cancelled_cb), task, NULL); } auth = g_slice_new0 (CheckAuthData); auth->username = g_strdup (username); auth->password = g_strdup (password); g_signal_connect_data (data->session, "authenticate", G_CALLBACK (http_client_authenticate), auth, http_client_check_auth_data_free, 0); g_signal_connect (data->session, "request-started", G_CALLBACK (http_client_request_started), task); soup_session_queue_message (data->session, data->msg, http_client_check_response_cb, g_object_ref (task)); g_object_unref (task); } gboolean goa_http_client_check_finish (GoaHttpClient *self, GAsyncResult *res, GError **error) { GTask *task; g_return_val_if_fail (GOA_IS_HTTP_CLIENT (self), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); g_return_val_if_fail (g_task_is_valid (res, self), FALSE); task = G_TASK (res); g_return_val_if_fail (g_task_get_source_tag (task) == goa_http_client_check, FALSE); return g_task_propagate_boolean (task, error); } /* ---------------------------------------------------------------------------------------------------- */ typedef struct { GError **error; GMainLoop *loop; gboolean op_res; } CheckSyncData; static void http_client_check_sync_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) { CheckSyncData *data = user_data; data->op_res = goa_http_client_check_finish (GOA_HTTP_CLIENT (source_object), res, data->error); g_main_loop_quit (data->loop); } gboolean goa_http_client_check_sync (GoaHttpClient *self, const gchar *uri, const gchar *username, const gchar *password, gboolean accept_ssl_errors, GCancellable *cancellable, GError **error) { CheckSyncData data; GMainContext *context = NULL; g_return_val_if_fail (GOA_IS_HTTP_CLIENT (self), FALSE); g_return_val_if_fail (uri != NULL && uri[0] != '\0', FALSE); g_return_val_if_fail (username != NULL && username[0] != '\0', FALSE); g_return_val_if_fail (password != NULL && password[0] != '\0', FALSE); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); data.error = error; context = g_main_context_new (); g_main_context_push_thread_default (context); data.loop = g_main_loop_new (context, FALSE); goa_http_client_check (self, uri, username, password, accept_ssl_errors, cancellable, http_client_check_sync_cb, &data); g_main_loop_run (data.loop); g_main_loop_unref (data.loop); g_main_context_pop_thread_default (context); g_main_context_unref (context); return data.op_res; }