Blame src/goabackend/goahttpclient.c

Packit 79f644
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
Packit 79f644
/*
Packit 79f644
 * Copyright © 2012 – 2017 Red Hat, Inc.
Packit 79f644
 *
Packit 79f644
 * This library is free software; you can redistribute it and/or
Packit 79f644
 * modify it under the terms of the GNU Lesser General Public
Packit 79f644
 * License as published by the Free Software Foundation; either
Packit 79f644
 * version 2 of the License, or (at your option) any later version.
Packit 79f644
 *
Packit 79f644
 * This library is distributed in the hope that it will be useful,
Packit 79f644
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 79f644
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit 79f644
 * Lesser General Public License for more details.
Packit 79f644
 *
Packit 79f644
 * You should have received a copy of the GNU Lesser General
Packit 79f644
 * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
Packit 79f644
 */
Packit 79f644
Packit 79f644
#include "config.h"
Packit 79f644
Packit 79f644
#include <libsoup/soup.h>
Packit 79f644
Packit 79f644
#include "goahttpclient.h"
Packit 79f644
#include "goasouplogger.h"
Packit 79f644
#include "goautils.h"
Packit 79f644
Packit 79f644
struct _GoaHttpClient
Packit 79f644
{
Packit 79f644
  GObject parent_instance;
Packit 79f644
};
Packit 79f644
Packit 79f644
G_DEFINE_TYPE (GoaHttpClient, goa_http_client, G_TYPE_OBJECT);
Packit 79f644
Packit 79f644
/* ---------------------------------------------------------------------------------------------------- */
Packit 79f644
Packit 79f644
static void
Packit 79f644
goa_http_client_init (GoaHttpClient *self)
Packit 79f644
{
Packit 79f644
}
Packit 79f644
Packit 79f644
static void
Packit 79f644
goa_http_client_class_init (GoaHttpClientClass *klass)
Packit 79f644
{
Packit 79f644
}
Packit 79f644
Packit 79f644
/* ---------------------------------------------------------------------------------------------------- */
Packit 79f644
Packit 79f644
GoaHttpClient *
Packit 79f644
goa_http_client_new (void)
Packit 79f644
{
Packit 79f644
  return GOA_HTTP_CLIENT (g_object_new (GOA_TYPE_HTTP_CLIENT, NULL));
Packit 79f644
}
Packit 79f644
Packit 79f644
/* ---------------------------------------------------------------------------------------------------- */
Packit 79f644
Packit 79f644
typedef struct
Packit 79f644
{
Packit 79f644
  GCancellable *cancellable;
Packit 79f644
  SoupMessage *msg;
Packit 79f644
  SoupSession *session;
Packit 79f644
  gboolean accept_ssl_errors;
Packit 79f644
  gulong cancellable_id;
Packit 79f644
} CheckData;
Packit 79f644
Packit 79f644
typedef struct
Packit 79f644
{
Packit 79f644
  gchar *password;
Packit 79f644
  gchar *username;
Packit 79f644
} CheckAuthData;
Packit 79f644
Packit 79f644
static void
Packit 79f644
http_client_check_data_free (gpointer user_data)
Packit 79f644
{
Packit 79f644
  CheckData *data = user_data;
Packit 79f644
Packit 79f644
  if (data->cancellable_id > 0)
Packit 79f644
    {
Packit 79f644
      g_cancellable_disconnect (data->cancellable, data->cancellable_id);
Packit 79f644
      g_object_unref (data->cancellable);
Packit 79f644
    }
Packit 79f644
Packit 79f644
  /* soup_session_queue_message stole the references to data->msg */
Packit 79f644
  g_object_unref (data->session);
Packit 79f644
  g_slice_free (CheckData, data);
Packit 79f644
}
Packit 79f644
Packit 79f644
static void
Packit 79f644
http_client_check_auth_data_free (gpointer data, GClosure *closure)
Packit 79f644
{
Packit 79f644
  CheckAuthData *auth = data;
Packit 79f644
Packit 79f644
  g_free (auth->password);
Packit 79f644
  g_free (auth->username);
Packit 79f644
  g_slice_free (CheckAuthData, auth);
Packit 79f644
}
Packit 79f644
Packit 79f644
static void
Packit 79f644
http_client_authenticate (SoupSession *session,
Packit 79f644
                         SoupMessage *msg,
Packit 79f644
                         SoupAuth *auth,
Packit 79f644
                         gboolean retrying,
Packit 79f644
                         gpointer user_data)
Packit 79f644
{
Packit 79f644
  CheckAuthData *data = user_data;
Packit 79f644
Packit 79f644
  if (retrying)
Packit 79f644
    return;
Packit 79f644
Packit 79f644
  soup_auth_authenticate (auth, data->username, data->password);
Packit 79f644
}
Packit 79f644
Packit 79f644
static void
Packit 79f644
http_client_request_started (SoupSession *session, SoupMessage *msg, SoupSocket *socket, gpointer user_data)
Packit 79f644
{
Packit 79f644
  CheckData *data;
Packit 79f644
  GTask *task = G_TASK (user_data);
Packit 79f644
  GError *error;
Packit 79f644
  GTlsCertificateFlags cert_flags;
Packit 79f644
Packit 79f644
  data = g_task_get_task_data (task);
Packit 79f644
  error = NULL;
Packit 79f644
Packit 79f644
  if (!data->accept_ssl_errors
Packit 79f644
      && soup_message_get_https_status (msg, NULL, &cert_flags)
Packit 79f644
      && cert_flags != 0)
Packit 79f644
    {
Packit 79f644
      goa_utils_set_error_ssl (&error, cert_flags);
Packit 79f644
      g_task_return_error (task, error);
Packit 79f644
      soup_session_abort (data->session);
Packit 79f644
    }
Packit 79f644
}
Packit 79f644
Packit 79f644
static void
Packit 79f644
http_client_check_cancelled_cb (GCancellable *cancellable, gpointer user_data)
Packit 79f644
{
Packit 79f644
  CheckData *data;
Packit 79f644
  GTask *task = G_TASK (user_data);
Packit 79f644
  gboolean cancelled;
Packit 79f644
Packit 79f644
  data = g_task_get_task_data (task);
Packit 79f644
Packit 79f644
  cancelled = g_task_return_error_if_cancelled (task);
Packit 79f644
  soup_session_abort (data->session);
Packit 79f644
Packit 79f644
  g_return_if_fail (cancelled);
Packit 79f644
}
Packit 79f644
Packit 79f644
static gboolean
Packit 79f644
http_client_check_free_in_idle (gpointer user_data)
Packit 79f644
{
Packit 79f644
  GTask *task = G_TASK (user_data);
Packit 79f644
Packit 79f644
  g_object_unref (task);
Packit 79f644
  return G_SOURCE_REMOVE;
Packit 79f644
}
Packit 79f644
Packit 79f644
static void
Packit 79f644
http_client_check_response_cb (SoupSession *session, SoupMessage *msg, gpointer user_data)
Packit 79f644
{
Packit 79f644
  GError *error;
Packit 79f644
  GMainContext *context;
Packit 79f644
  GSource *source;
Packit 79f644
  GTask *task = G_TASK (user_data);
Packit 79f644
Packit 79f644
  error = NULL;
Packit 79f644
Packit 79f644
  /* status == SOUP_STATUS_CANCELLED, if we are being aborted by the
Packit 79f644
   * GCancellable or due to an SSL error. The GTask was already
Packit 79f644
   * 'returned' by the respective callbacks.
Packit 79f644
   */
Packit 79f644
  if (msg->status_code == SOUP_STATUS_CANCELLED)
Packit 79f644
    goto out;
Packit 79f644
  else if (msg->status_code != SOUP_STATUS_OK)
Packit 79f644
    {
Packit 79f644
      g_warning ("goa_http_client_check() failed: %u — %s", msg->status_code, msg->reason_phrase);
Packit 79f644
      goa_utils_set_error_soup (&error, msg);
Packit 79f644
      g_task_return_error (task, error);
Packit 79f644
      goto out;
Packit 79f644
    }
Packit 79f644
Packit 79f644
  g_task_return_boolean (task, TRUE);
Packit 79f644
Packit 79f644
 out:
Packit 79f644
  /* We might be invoked from a GCancellable::cancelled
Packit 79f644
   * handler, and unreffing the GTask will disconnect the
Packit 79f644
   * handler. Since disconnecting from inside the handler will cause a
Packit 79f644
   * deadlock [1], we use an idle handler to break them up.
Packit 79f644
   *
Packit 79f644
   * [1] https://bugzilla.gnome.org/show_bug.cgi?id=705395
Packit 79f644
   */
Packit 79f644
Packit 79f644
  source = g_idle_source_new ();
Packit 79f644
  g_source_set_priority (source, G_PRIORITY_DEFAULT_IDLE);
Packit 79f644
  g_source_set_callback (source, http_client_check_free_in_idle, task, NULL);
Packit 79f644
  g_source_set_name (source, "[goa] http_client_check_free_in_idle");
Packit 79f644
Packit 79f644
  context = g_task_get_context (task);
Packit 79f644
  g_source_attach (source, context);
Packit 79f644
  g_source_unref (source);
Packit 79f644
}
Packit 79f644
Packit 79f644
void
Packit 79f644
goa_http_client_check (GoaHttpClient       *self,
Packit 79f644
                       const gchar         *uri,
Packit 79f644
                       const gchar         *username,
Packit 79f644
                       const gchar         *password,
Packit 79f644
                       gboolean             accept_ssl_errors,
Packit 79f644
                       GCancellable        *cancellable,
Packit 79f644
                       GAsyncReadyCallback  callback,
Packit 79f644
                       gpointer             user_data)
Packit 79f644
{
Packit 79f644
  CheckData *data;
Packit 79f644
  CheckAuthData *auth;
Packit 79f644
  GTask *task;
Packit 79f644
  SoupLogger *logger;
Packit 79f644
Packit 79f644
  g_return_if_fail (GOA_IS_HTTP_CLIENT (self));
Packit 79f644
  g_return_if_fail (uri != NULL && uri[0] != '\0');
Packit 79f644
  g_return_if_fail (username != NULL && username[0] != '\0');
Packit 79f644
  g_return_if_fail (password != NULL && password[0] != '\0');
Packit 79f644
  g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
Packit 79f644
Packit 79f644
  task = g_task_new (self, cancellable, callback, user_data);
Packit 79f644
  g_task_set_source_tag (task, goa_http_client_check);
Packit 79f644
Packit 79f644
  data = g_slice_new0 (CheckData);
Packit 79f644
  g_task_set_task_data (task, data, http_client_check_data_free);
Packit 79f644
Packit 79f644
  data->session = soup_session_new_with_options (SOUP_SESSION_SSL_STRICT, FALSE,
Packit 79f644
                                                 NULL);
Packit 79f644
Packit 79f644
  logger = goa_soup_logger_new (SOUP_LOGGER_LOG_BODY, -1);
Packit 79f644
  soup_session_add_feature (data->session, SOUP_SESSION_FEATURE (logger));
Packit 79f644
  g_object_unref (logger);
Packit 79f644
Packit 79f644
  data->accept_ssl_errors = accept_ssl_errors;
Packit 79f644
  data->msg = soup_message_new (SOUP_METHOD_GET, uri);
Packit 79f644
Packit 79f644
  if (cancellable != NULL)
Packit 79f644
    {
Packit 79f644
      data->cancellable = g_object_ref (cancellable);
Packit 79f644
      data->cancellable_id = g_cancellable_connect (cancellable,
Packit 79f644
                                                    G_CALLBACK (http_client_check_cancelled_cb),
Packit 79f644
                                                    task,
Packit 79f644
                                                    NULL);
Packit 79f644
    }
Packit 79f644
Packit 79f644
  auth = g_slice_new0 (CheckAuthData);
Packit 79f644
  auth->username = g_strdup (username);
Packit 79f644
  auth->password = g_strdup (password);
Packit 79f644
  g_signal_connect_data (data->session,
Packit 79f644
                         "authenticate",
Packit 79f644
                         G_CALLBACK (http_client_authenticate),
Packit 79f644
                         auth,
Packit 79f644
                         http_client_check_auth_data_free,
Packit 79f644
                         0);
Packit 79f644
Packit 79f644
  g_signal_connect (data->session, "request-started", G_CALLBACK (http_client_request_started), task);
Packit 79f644
  soup_session_queue_message (data->session, data->msg, http_client_check_response_cb, g_object_ref (task));
Packit 79f644
Packit 79f644
  g_object_unref (task);
Packit 79f644
}
Packit 79f644
Packit 79f644
gboolean
Packit 79f644
goa_http_client_check_finish (GoaHttpClient *self, GAsyncResult *res, GError **error)
Packit 79f644
{
Packit 79f644
  GTask *task;
Packit 79f644
Packit 79f644
  g_return_val_if_fail (GOA_IS_HTTP_CLIENT (self), FALSE);
Packit 79f644
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
Packit 79f644
Packit 79f644
  g_return_val_if_fail (g_task_is_valid (res, self), FALSE);
Packit 79f644
  task = G_TASK (res);
Packit 79f644
Packit 79f644
  g_return_val_if_fail (g_task_get_source_tag (task) == goa_http_client_check, FALSE);
Packit 79f644
Packit 79f644
  return g_task_propagate_boolean (task, error);
Packit 79f644
}
Packit 79f644
Packit 79f644
/* ---------------------------------------------------------------------------------------------------- */
Packit 79f644
Packit 79f644
typedef struct
Packit 79f644
{
Packit 79f644
  GError **error;
Packit 79f644
  GMainLoop *loop;
Packit 79f644
  gboolean op_res;
Packit 79f644
} CheckSyncData;
Packit 79f644
Packit 79f644
static void
Packit 79f644
http_client_check_sync_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
Packit 79f644
{
Packit 79f644
  CheckSyncData *data = user_data;
Packit 79f644
Packit 79f644
  data->op_res = goa_http_client_check_finish (GOA_HTTP_CLIENT (source_object), res, data->error);
Packit 79f644
  g_main_loop_quit (data->loop);
Packit 79f644
}
Packit 79f644
Packit 79f644
gboolean
Packit 79f644
goa_http_client_check_sync (GoaHttpClient       *self,
Packit 79f644
                            const gchar         *uri,
Packit 79f644
                            const gchar         *username,
Packit 79f644
                            const gchar         *password,
Packit 79f644
                            gboolean             accept_ssl_errors,
Packit 79f644
                            GCancellable        *cancellable,
Packit 79f644
                            GError             **error)
Packit 79f644
{
Packit 79f644
  CheckSyncData data;
Packit 79f644
  GMainContext *context = NULL;
Packit 79f644
Packit 79f644
  g_return_val_if_fail (GOA_IS_HTTP_CLIENT (self), FALSE);
Packit 79f644
  g_return_val_if_fail (uri != NULL && uri[0] != '\0', FALSE);
Packit 79f644
  g_return_val_if_fail (username != NULL && username[0] != '\0', FALSE);
Packit 79f644
  g_return_val_if_fail (password != NULL && password[0] != '\0', FALSE);
Packit 79f644
  g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
Packit 79f644
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
Packit 79f644
Packit 79f644
  data.error = error;
Packit 79f644
Packit 79f644
  context = g_main_context_new ();
Packit 79f644
  g_main_context_push_thread_default (context);
Packit 79f644
  data.loop = g_main_loop_new (context, FALSE);
Packit 79f644
Packit 79f644
  goa_http_client_check (self,
Packit 79f644
                         uri,
Packit 79f644
                         username,
Packit 79f644
                         password,
Packit 79f644
                         accept_ssl_errors,
Packit 79f644
                         cancellable,
Packit 79f644
                         http_client_check_sync_cb,
Packit 79f644
                         &data);
Packit 79f644
  g_main_loop_run (data.loop);
Packit 79f644
  g_main_loop_unref (data.loop);
Packit 79f644
Packit 79f644
  g_main_context_pop_thread_default (context);
Packit 79f644
  g_main_context_unref (context);
Packit 79f644
Packit 79f644
  return data.op_res;
Packit 79f644
}