Blame src/glade-http.c

Packit 1e8aac
/*
Packit 1e8aac
 * Copyright (C) 2014 Juan Pablo Ugarte.
Packit 1e8aac
   *
Packit 1e8aac
 * This program is free software; you can redistribute it and/or modify
Packit 1e8aac
 * it under the terms of the GNU General Public License as
Packit 1e8aac
 * published by the Free Software Foundation; either version 2 of the
Packit 1e8aac
 * License, or (at your option) any later version.
Packit 1e8aac
   *
Packit 1e8aac
 * This program is distributed in the hope that it will be useful,
Packit 1e8aac
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 1e8aac
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit 1e8aac
 * GNU General Public License for more details.
Packit 1e8aac
 *
Packit 1e8aac
 * You should have received a copy of the GNU General Public License
Packit 1e8aac
 * along with this program; if not, write to the Free Software
Packit 1e8aac
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Packit 1e8aac
 *
Packit 1e8aac
 * Authors:
Packit 1e8aac
 *   Juan Pablo Ugarte <juanpablougarte@gmail.com>
Packit 1e8aac
 */
Packit 1e8aac
Packit 1e8aac
#include <glade-http.h>
Packit 1e8aac
#include <string.h>
Packit 1e8aac
#include <stdio.h>
Packit 1e8aac
Packit 1e8aac
#define HTTP_BUFFER_SIZE 16
Packit 1e8aac
Packit 1e8aac
struct _GladeHTTPPrivate
Packit 1e8aac
{
Packit 1e8aac
  gchar *host;
Packit 1e8aac
  gint port;
Packit 1e8aac
  gboolean tls;
Packit 1e8aac
Packit 1e8aac
  GladeHTTPStatus status;
Packit 1e8aac
  GSocketConnection *connection;
Packit 1e8aac
  GCancellable *cancellable;
Packit 1e8aac
  
Packit 1e8aac
  GString *data;
Packit 1e8aac
  GString *response;
Packit 1e8aac
Packit 1e8aac
  gchar response_buffer[HTTP_BUFFER_SIZE];
Packit 1e8aac
};
Packit 1e8aac
Packit 1e8aac
enum
Packit 1e8aac
{
Packit 1e8aac
  PROP_0,
Packit 1e8aac
Packit 1e8aac
  PROP_HOST,
Packit 1e8aac
  PROP_PORT,
Packit 1e8aac
  PROP_TLS,
Packit 1e8aac
  N_PROPERTIES
Packit 1e8aac
};
Packit 1e8aac
Packit 1e8aac
enum
Packit 1e8aac
{
Packit 1e8aac
  REQUEST_DONE,
Packit 1e8aac
  STATUS,
Packit 1e8aac
Packit 1e8aac
  LAST_SIGNAL
Packit 1e8aac
};
Packit 1e8aac
Packit 1e8aac
static GParamSpec *properties[N_PROPERTIES];
Packit 1e8aac
static guint http_signals[LAST_SIGNAL] = { 0 };
Packit 1e8aac
Packit 1e8aac
G_DEFINE_TYPE_WITH_PRIVATE (GladeHTTP, glade_http, G_TYPE_OBJECT);
Packit 1e8aac
Packit 1e8aac
static void
Packit 1e8aac
glade_http_emit_request_done (GladeHTTP *http, gchar *response)
Packit 1e8aac
{
Packit 1e8aac
  gint http_major, http_minor, code, i;
Packit 1e8aac
  gchar **headers, **values, *blank, *data, tmp;
Packit 1e8aac
Packit 1e8aac
  if ((blank = g_strstr_len (response, -1, "\r\n\r\n")))
Packit 1e8aac
    data = blank + 4;
Packit 1e8aac
  else if ((blank = g_strstr_len (response, -1, "\n\n")))
Packit 1e8aac
    data = blank + 2;
Packit 1e8aac
  else
Packit 1e8aac
    return;
Packit 1e8aac
Packit 1e8aac
  tmp = *blank;
Packit 1e8aac
  *blank = '\0';
Packit 1e8aac
  headers = g_strsplit (response, "\n", 0);
Packit 1e8aac
  *blank = tmp;
Packit 1e8aac
Packit 1e8aac
  values = g_new0 (gchar *, g_strv_length (headers));
Packit 1e8aac
  
Packit 1e8aac
  for (i = 0; headers[i]; i++)
Packit 1e8aac
    {
Packit 1e8aac
      g_strchug (headers[i]);
Packit 1e8aac
Packit 1e8aac
      if (i)
Packit 1e8aac
        {
Packit 1e8aac
          gchar *colon = g_strstr_len (headers[i], -1, ":");
Packit 1e8aac
Packit 1e8aac
          if (colon)
Packit 1e8aac
            {
Packit 1e8aac
              *colon++ = '\0';
Packit 1e8aac
              values[i-1] = g_strstrip (colon);
Packit 1e8aac
            }
Packit 1e8aac
          else
Packit 1e8aac
            values[i-1] = "";
Packit 1e8aac
        }
Packit 1e8aac
      else
Packit 1e8aac
        {
Packit 1e8aac
          if (sscanf (response, "HTTP/%d.%d %d", &http_major, &http_minor, &code) != 3)
Packit 1e8aac
            http_major = http_minor = code = 0;
Packit 1e8aac
        }
Packit 1e8aac
    }
Packit 1e8aac
  
Packit 1e8aac
  /* emit request-done */
Packit 1e8aac
  g_signal_emit (http, http_signals[REQUEST_DONE], 0, code,
Packit 1e8aac
                 (headers[0]) ? &headers[1] : NULL, values, 
Packit 1e8aac
                 data);
Packit 1e8aac
Packit 1e8aac
  g_strfreev (headers);
Packit 1e8aac
  g_free (values);
Packit 1e8aac
}
Packit 1e8aac
Packit 1e8aac
static void
Packit 1e8aac
glade_http_emit_status (GladeHTTP *http, GladeHTTPStatus status, GError *error)
Packit 1e8aac
{
Packit 1e8aac
  GladeHTTPPrivate *priv = http->priv;
Packit 1e8aac
Packit 1e8aac
  if (priv->status == status)
Packit 1e8aac
    return;
Packit 1e8aac
Packit 1e8aac
  priv->status = status;
Packit 1e8aac
Packit 1e8aac
  g_signal_emit (http, http_signals[STATUS], 0, status, error);
Packit 1e8aac
}
Packit 1e8aac
Packit 1e8aac
static void
Packit 1e8aac
on_read_ready (GObject *source, GAsyncResult *res, gpointer data)
Packit 1e8aac
{
Packit 1e8aac
  GladeHTTPPrivate *priv = GLADE_HTTP (data)->priv;
Packit 1e8aac
  GError *error = NULL;
Packit 1e8aac
  gssize bytes_read;
Packit 1e8aac
Packit 1e8aac
  glade_http_emit_status (data, GLADE_HTTP_RECEIVING, NULL);
Packit 1e8aac
Packit 1e8aac
  bytes_read = g_input_stream_read_finish (G_INPUT_STREAM (source), res, &error);
Packit 1e8aac
  
Packit 1e8aac
  if (bytes_read > 0)
Packit 1e8aac
    {
Packit 1e8aac
      g_string_append_len (priv->response, priv->response_buffer, bytes_read);
Packit 1e8aac
Packit 1e8aac
      /* NOTE: We do not need to parse content-lenght because we do not support
Packit 1e8aac
       * multiples HTTP requests in the same connection.
Packit 1e8aac
       */
Packit 1e8aac
      if (priv->cancellable)
Packit 1e8aac
        g_cancellable_reset (priv->cancellable);
Packit 1e8aac
      
Packit 1e8aac
      g_input_stream_read_async (g_io_stream_get_input_stream (G_IO_STREAM (priv->connection)),
Packit 1e8aac
                                 priv->response_buffer, HTTP_BUFFER_SIZE,
Packit 1e8aac
                                 G_PRIORITY_DEFAULT, priv->cancellable,
Packit 1e8aac
                                 on_read_ready, data);
Packit 1e8aac
      return;
Packit 1e8aac
    }
Packit 1e8aac
  else if (bytes_read < 0)
Packit 1e8aac
    {
Packit 1e8aac
      glade_http_emit_status (data, GLADE_HTTP_ERROR, error);
Packit 1e8aac
      g_error_free (error);
Packit 1e8aac
      return;
Packit 1e8aac
    }
Packit 1e8aac
Packit 1e8aac
  /* emit request-done */
Packit 1e8aac
  glade_http_emit_request_done (data, priv->response->str);
Packit 1e8aac
  glade_http_emit_status (data, GLADE_HTTP_READY, NULL);
Packit 1e8aac
}
Packit 1e8aac
Packit 1e8aac
static void
Packit 1e8aac
on_write_ready (GObject *source, GAsyncResult *res, gpointer data)
Packit 1e8aac
{
Packit 1e8aac
  GladeHTTPPrivate *priv = GLADE_HTTP (data)->priv;
Packit 1e8aac
  GError *error = NULL;
Packit 1e8aac
  gsize count;
Packit 1e8aac
Packit 1e8aac
  count = g_output_stream_write_finish (G_OUTPUT_STREAM (source), res, &error);
Packit 1e8aac
Packit 1e8aac
  if (error == NULL && priv->data->len != count)
Packit 1e8aac
    error = g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED, 
Packit 1e8aac
                         "Error sending data data to %s", 
Packit 1e8aac
                         priv->host);
Packit 1e8aac
Packit 1e8aac
  if (error)
Packit 1e8aac
    {
Packit 1e8aac
      glade_http_emit_status (data, GLADE_HTTP_ERROR, error);
Packit 1e8aac
      g_error_free (error);
Packit 1e8aac
      return;
Packit 1e8aac
    }
Packit 1e8aac
Packit 1e8aac
  glade_http_emit_status (data, GLADE_HTTP_WAITING, NULL);
Packit 1e8aac
  g_input_stream_read_async (g_io_stream_get_input_stream (G_IO_STREAM (priv->connection)),
Packit 1e8aac
                             priv->response_buffer, HTTP_BUFFER_SIZE,
Packit 1e8aac
                             G_PRIORITY_DEFAULT, priv->cancellable, on_read_ready, data);
Packit 1e8aac
}
Packit 1e8aac
Packit 1e8aac
static void
Packit 1e8aac
on_connect_ready (GObject *source, GAsyncResult *res, gpointer data)
Packit 1e8aac
{
Packit 1e8aac
  GladeHTTPPrivate *priv = GLADE_HTTP (data)->priv;
Packit 1e8aac
  GError *error = NULL;
Packit 1e8aac
Packit 1e8aac
  priv->connection = g_socket_client_connect_to_host_finish (G_SOCKET_CLIENT (source), res, &error);
Packit 1e8aac
Packit 1e8aac
  if (error)
Packit 1e8aac
    {
Packit 1e8aac
      glade_http_emit_status (data, GLADE_HTTP_ERROR, error);
Packit 1e8aac
      g_error_free (error);
Packit 1e8aac
      return;
Packit 1e8aac
    }
Packit 1e8aac
Packit 1e8aac
  if (priv->connection)
Packit 1e8aac
    {
Packit 1e8aac
      glade_http_emit_status (data, GLADE_HTTP_SENDING, NULL);
Packit 1e8aac
      g_output_stream_write_async (g_io_stream_get_output_stream (G_IO_STREAM (priv->connection)),
Packit 1e8aac
                                   priv->data->str, priv->data->len, G_PRIORITY_DEFAULT,
Packit 1e8aac
                                   priv->cancellable, on_write_ready, data);
Packit 1e8aac
    }
Packit 1e8aac
}
Packit 1e8aac
Packit 1e8aac
static void
Packit 1e8aac
glade_http_init (GladeHTTP *http)
Packit 1e8aac
{
Packit 1e8aac
  GladeHTTPPrivate *priv;
Packit 1e8aac
  
Packit 1e8aac
  priv = http->priv = glade_http_get_instance_private (http);
Packit 1e8aac
Packit 1e8aac
  priv->data = g_string_new ("");
Packit 1e8aac
  priv->response = g_string_new ("");
Packit 1e8aac
}
Packit 1e8aac
Packit 1e8aac
static void
Packit 1e8aac
glade_http_clear (GladeHTTP *http)
Packit 1e8aac
{
Packit 1e8aac
  GladeHTTPPrivate *priv = http->priv;
Packit 1e8aac
Packit 1e8aac
  if (priv->cancellable)
Packit 1e8aac
    g_cancellable_cancel (priv->cancellable);
Packit 1e8aac
Packit 1e8aac
  g_clear_object (&priv->connection);
Packit 1e8aac
  g_clear_object (&priv->cancellable);
Packit 1e8aac
Packit 1e8aac
  g_string_assign (priv->data, "");
Packit 1e8aac
  g_string_assign (priv->response, "");
Packit 1e8aac
Packit 1e8aac
  priv->status = GLADE_HTTP_READY;
Packit 1e8aac
}
Packit 1e8aac
Packit 1e8aac
static void
Packit 1e8aac
glade_http_finalize (GObject *object)
Packit 1e8aac
{
Packit 1e8aac
  GladeHTTPPrivate *priv = GLADE_HTTP (object)->priv;
Packit 1e8aac
Packit 1e8aac
  glade_http_clear (GLADE_HTTP (object));
Packit 1e8aac
  
Packit 1e8aac
  g_string_free (priv->data, TRUE);
Packit 1e8aac
  g_string_free (priv->response, TRUE);
Packit 1e8aac
  
Packit 1e8aac
  G_OBJECT_CLASS (glade_http_parent_class)->finalize (object);
Packit 1e8aac
}
Packit 1e8aac
Packit 1e8aac
static void
Packit 1e8aac
glade_http_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
Packit 1e8aac
{
Packit 1e8aac
  GladeHTTPPrivate *priv;
Packit 1e8aac
  
Packit 1e8aac
  g_return_if_fail (GLADE_IS_HTTP (object));
Packit 1e8aac
  priv = GLADE_HTTP (object)->priv;
Packit 1e8aac
  
Packit 1e8aac
  switch (prop_id)
Packit 1e8aac
    {
Packit 1e8aac
      case PROP_HOST:
Packit 1e8aac
        g_free (priv->host);
Packit 1e8aac
        priv->host = g_strdup (g_value_get_string (value));
Packit 1e8aac
        break;
Packit 1e8aac
      case PROP_PORT:
Packit 1e8aac
        priv->port = g_value_get_int (value);
Packit 1e8aac
        break;
Packit 1e8aac
      case PROP_TLS:
Packit 1e8aac
        priv->tls = g_value_get_boolean (value);
Packit 1e8aac
        break;
Packit 1e8aac
      default:
Packit 1e8aac
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
Packit 1e8aac
        break;
Packit 1e8aac
    }
Packit 1e8aac
}
Packit 1e8aac
Packit 1e8aac
static void
Packit 1e8aac
glade_http_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
Packit 1e8aac
{
Packit 1e8aac
  GladeHTTPPrivate *priv;
Packit 1e8aac
  
Packit 1e8aac
  g_return_if_fail (GLADE_IS_HTTP (object));
Packit 1e8aac
  priv = GLADE_HTTP (object)->priv;
Packit 1e8aac
Packit 1e8aac
  switch (prop_id)
Packit 1e8aac
    {
Packit 1e8aac
      case PROP_HOST:
Packit 1e8aac
        g_value_set_string (value, priv->host);
Packit 1e8aac
        break;
Packit 1e8aac
      case PROP_PORT:
Packit 1e8aac
        g_value_set_int (value, priv->port);
Packit 1e8aac
        break;
Packit 1e8aac
      case PROP_TLS:
Packit 1e8aac
        g_value_set_boolean (value, priv->tls);
Packit 1e8aac
        break;
Packit 1e8aac
      default:
Packit 1e8aac
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
Packit 1e8aac
        break;
Packit 1e8aac
    }
Packit 1e8aac
}
Packit 1e8aac
Packit 1e8aac
static void
Packit 1e8aac
glade_http_class_init (GladeHTTPClass *klass)
Packit 1e8aac
{
Packit 1e8aac
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
Packit 1e8aac
Packit 1e8aac
  object_class->finalize = glade_http_finalize;
Packit 1e8aac
  object_class->set_property = glade_http_set_property;
Packit 1e8aac
  object_class->get_property = glade_http_get_property;
Packit 1e8aac
Packit 1e8aac
  properties[PROP_HOST] =
Packit 1e8aac
    g_param_spec_string ("host", "Host",
Packit 1e8aac
                         "Host to connect to",
Packit 1e8aac
                         NULL, G_PARAM_READWRITE);
Packit 1e8aac
Packit 1e8aac
  properties[PROP_PORT] =
Packit 1e8aac
    g_param_spec_int ("port", "TCP Port",
Packit 1e8aac
                      "TCP port to connect to",
Packit 1e8aac
                      0, 65536, 80, G_PARAM_READWRITE);
Packit 1e8aac
Packit 1e8aac
  properties[PROP_TLS] =
Packit 1e8aac
    g_param_spec_boolean ("tls", "TLS",
Packit 1e8aac
                          "Wheter to use tls encryption or not",
Packit 1e8aac
                          FALSE, G_PARAM_READWRITE);
Packit 1e8aac
Packit 1e8aac
  /* Install all properties */
Packit 1e8aac
  g_object_class_install_properties (object_class, N_PROPERTIES, properties);
Packit 1e8aac
  
Packit 1e8aac
  http_signals[REQUEST_DONE] =
Packit 1e8aac
    g_signal_new ("request-done",
Packit 1e8aac
                  G_OBJECT_CLASS_TYPE (klass), 0,
Packit 1e8aac
                  G_STRUCT_OFFSET (GladeHTTPClass, request_done),
Packit 1e8aac
                  NULL, NULL, NULL,
Packit 1e8aac
                  G_TYPE_NONE, 4,
Packit 1e8aac
                  G_TYPE_INT, G_TYPE_STRV, G_TYPE_STRV, G_TYPE_STRING);
Packit 1e8aac
Packit 1e8aac
  http_signals[STATUS] =
Packit 1e8aac
    g_signal_new ("status",
Packit 1e8aac
                  G_OBJECT_CLASS_TYPE (klass), 0,
Packit 1e8aac
                  G_STRUCT_OFFSET (GladeHTTPClass, status),
Packit 1e8aac
                  NULL, NULL, NULL,
Packit 1e8aac
                  G_TYPE_NONE, 2,
Packit 1e8aac
                  G_TYPE_INT, G_TYPE_ERROR);
Packit 1e8aac
}
Packit 1e8aac
Packit 1e8aac
GladeHTTP *
Packit 1e8aac
glade_http_new (const gchar *host, gint port, gboolean tls)
Packit 1e8aac
{
Packit 1e8aac
  return GLADE_HTTP (g_object_new (GLADE_TYPE_HTTP,
Packit 1e8aac
                                   "host", host,
Packit 1e8aac
                                   "port", port,
Packit 1e8aac
                                   "tls", tls,
Packit 1e8aac
                                   NULL));
Packit 1e8aac
}
Packit 1e8aac
Packit 1e8aac
const gchar *
Packit 1e8aac
glade_http_get_host (GladeHTTP *http)
Packit 1e8aac
{
Packit 1e8aac
  g_return_val_if_fail (GLADE_IS_HTTP (http), NULL);
Packit 1e8aac
  return http->priv->host;
Packit 1e8aac
}
Packit 1e8aac
Packit 1e8aac
gint
Packit 1e8aac
glade_http_get_port (GladeHTTP *http)
Packit 1e8aac
{
Packit 1e8aac
  g_return_val_if_fail (GLADE_IS_HTTP (http), 0);
Packit 1e8aac
  return http->priv->port;
Packit 1e8aac
}
Packit 1e8aac
Packit 1e8aac
void
Packit 1e8aac
glade_http_request_send_async (GladeHTTP    *http,
Packit 1e8aac
                               GCancellable *cancellable,
Packit 1e8aac
                               const gchar  *format,
Packit 1e8aac
                               ...)
Packit 1e8aac
{
Packit 1e8aac
  GladeHTTPPrivate *priv;
Packit 1e8aac
  GSocketClient *client;
Packit 1e8aac
  va_list ap;
Packit 1e8aac
Packit 1e8aac
  g_return_if_fail (GLADE_IS_HTTP (http));
Packit 1e8aac
Packit 1e8aac
  priv = http->priv;
Packit 1e8aac
  client = g_socket_client_new ();
Packit 1e8aac
  glade_http_clear (http);
Packit 1e8aac
Packit 1e8aac
  va_start (ap, format);
Packit 1e8aac
  g_string_vprintf (priv->data, format, ap);
Packit 1e8aac
  va_end (ap);
Packit 1e8aac
Packit 1e8aac
  priv->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
Packit 1e8aac
Packit 1e8aac
  if (priv->tls)
Packit 1e8aac
    {
Packit 1e8aac
      g_socket_client_set_tls (client, TRUE);
Packit 1e8aac
      g_socket_client_set_tls_validation_flags (client, 0);
Packit 1e8aac
    }
Packit 1e8aac
Packit 1e8aac
  glade_http_emit_status (http, GLADE_HTTP_CONNECTING, NULL);
Packit 1e8aac
  
Packit 1e8aac
  g_socket_client_connect_to_host_async (client,
Packit 1e8aac
                                         priv->host,
Packit 1e8aac
                                         priv->port,
Packit 1e8aac
                                         cancellable,
Packit 1e8aac
                                         on_connect_ready,
Packit 1e8aac
                                         http);
Packit 1e8aac
  g_object_unref (client);
Packit 1e8aac
}